From 97d40e93916cdd9da2f411d3ebc003718cf9e0ea Mon Sep 17 00:00:00 2001 From: Lukasz Gut <40406905+Blinkuu@users.noreply.github.com> Date: Fri, 23 Jun 2023 13:17:30 +0200 Subject: [PATCH] output/cloudv2: Implement RequestMetadata output (#3100) It adds support for flushing the tracing metadata to the related Cloud's remote gRPC service. --------- Co-authored-by: Ivan <2103732+codebien@users.noreply.github.com> --- cloudapi/config.go | 34 +- cloudapi/config_test.go | 5 +- cloudapi/insights/client.go | 207 +++++++ cloudapi/insights/client_test.go | 342 ++++++++++++ cloudapi/insights/doc.go | 3 + cloudapi/insights/domain.go | 41 ++ cloudapi/insights/mappers.go | 67 +++ cloudapi/insights/mappers_test.go | 70 +++ cloudapi/insights/proto/gen.go | 11 + .../insights/proto/v1/common/common.pb.go | 510 +++++++++++++++++ .../insights/proto/v1/common/common.proto | 65 +++ .../insights/proto/v1/ingester/ingester.pb.go | 525 ++++++++++++++++++ .../insights/proto/v1/ingester/ingester.proto | 39 ++ .../proto/v1/ingester/ingester_grpc.pb.go | 146 +++++ cloudapi/insights/proto/v1/k6/labels.pb.go | 246 ++++++++ cloudapi/insights/proto/v1/k6/labels.proto | 17 + .../proto/v1/k6/request_metadata.pb.go | 224 ++++++++ .../proto/v1/k6/request_metadata.proto | 17 + cloudapi/insights/proto/v1/trace/labels.pb.go | 187 +++++++ cloudapi/insights/proto/v1/trace/labels.proto | 14 + cloudapi/insights/proto/v1/trace/span.pb.go | 391 +++++++++++++ cloudapi/insights/proto/v1/trace/span.proto | 72 +++ output/cloud/expv2/collect.go | 103 ++++ output/cloud/expv2/collect_test.go | 139 +++++ output/cloud/expv2/flush.go | 28 + output/cloud/expv2/flush_test.go | 148 +++++ output/cloud/expv2/output.go | 90 ++- .../grpc/test/bufconn/bufconn.go | 318 +++++++++++ vendor/modules.txt | 1 + 29 files changed, 4051 insertions(+), 9 deletions(-) create mode 100644 cloudapi/insights/client.go create mode 100644 cloudapi/insights/client_test.go create mode 100644 cloudapi/insights/doc.go create mode 100644 cloudapi/insights/domain.go create mode 100644 cloudapi/insights/mappers.go create mode 100644 cloudapi/insights/mappers_test.go create mode 100644 cloudapi/insights/proto/gen.go create mode 100644 cloudapi/insights/proto/v1/common/common.pb.go create mode 100644 cloudapi/insights/proto/v1/common/common.proto create mode 100644 cloudapi/insights/proto/v1/ingester/ingester.pb.go create mode 100644 cloudapi/insights/proto/v1/ingester/ingester.proto create mode 100644 cloudapi/insights/proto/v1/ingester/ingester_grpc.pb.go create mode 100644 cloudapi/insights/proto/v1/k6/labels.pb.go create mode 100644 cloudapi/insights/proto/v1/k6/labels.proto create mode 100644 cloudapi/insights/proto/v1/k6/request_metadata.pb.go create mode 100644 cloudapi/insights/proto/v1/k6/request_metadata.proto create mode 100644 cloudapi/insights/proto/v1/trace/labels.pb.go create mode 100644 cloudapi/insights/proto/v1/trace/labels.proto create mode 100644 cloudapi/insights/proto/v1/trace/span.pb.go create mode 100644 cloudapi/insights/proto/v1/trace/span.proto create mode 100644 vendor/google.golang.org/grpc/test/bufconn/bufconn.go diff --git a/cloudapi/config.go b/cloudapi/config.go index 2eb8e25c8a1..be2331490c2 100644 --- a/cloudapi/config.go +++ b/cloudapi/config.go @@ -7,6 +7,7 @@ import ( "gopkg.in/guregu/null.v3" "github.com/mstoykov/envconfig" + "go.k6.io/k6/lib/types" ) @@ -40,6 +41,15 @@ type Config struct { // This is how many concurrent pushes will be done at the same time to the cloud MetricPushConcurrency null.Int `json:"metricPushConcurrency" envconfig:"K6_CLOUD_METRIC_PUSH_CONCURRENCY"` + // Indicates whether to send traces to the k6 Insights backend service. + TracesEnabled null.Bool `json:"tracesEnabled" envconfig:"K6_CLOUD_TRACES_ENABLED"` + + // The host of the k6 Insights backend service. + TracesHost null.String `json:"traceHost" envconfig:"K6_CLOUD_TRACES_HOST"` + + // The time interval between periodic API calls for sending samples to the cloud ingest service. + TracesPushInterval types.NullDuration `json:"tracesPushInterval" envconfig:"K6_CLOUD_TRACES_PUSH_INTERVAL"` + // Aggregation docs: // // If AggregationPeriod is specified and if it is greater than 0, HTTP metric aggregation @@ -145,11 +155,16 @@ type Config struct { // NewConfig creates a new Config instance with default values for some fields. func NewConfig() Config { return Config{ - Host: null.NewString("https://ingest.k6.io", false), - LogsTailURL: null.NewString("wss://cloudlogs.k6.io/api/v1/tail", false), - WebAppURL: null.NewString("https://app.k6.io", false), - MetricPushInterval: types.NewNullDuration(1*time.Second, false), - MetricPushConcurrency: null.NewInt(1, false), + Host: null.NewString("https://ingest.k6.io", false), + LogsTailURL: null.NewString("wss://cloudlogs.k6.io/api/v1/tail", false), + WebAppURL: null.NewString("https://app.k6.io", false), + MetricPushInterval: types.NewNullDuration(1*time.Second, false), + MetricPushConcurrency: null.NewInt(1, false), + + TracesEnabled: null.NewBool(false, false), + TracesHost: null.NewString("insights.k6.io:4443", false), + TracesPushInterval: types.NewNullDuration(1*time.Second, false), + MaxMetricSamplesPerPackage: null.NewInt(100000, false), Timeout: types.NewNullDuration(1*time.Minute, false), APIVersion: null.NewInt(1, false), @@ -216,6 +231,15 @@ func (c Config) Apply(cfg Config) Config { if cfg.MetricPushConcurrency.Valid { c.MetricPushConcurrency = cfg.MetricPushConcurrency } + if cfg.TracesEnabled.Valid { + c.TracesEnabled = cfg.TracesEnabled + } + if cfg.TracesHost.Valid { + c.TracesHost = cfg.TracesHost + } + if cfg.TracesPushInterval.Valid { + c.TracesPushInterval = cfg.TracesPushInterval + } if cfg.AggregationPeriod.Valid { c.AggregationPeriod = cfg.AggregationPeriod } diff --git a/cloudapi/config_test.go b/cloudapi/config_test.go index 4dac5fc768a..6c6edb8f356 100644 --- a/cloudapi/config_test.go +++ b/cloudapi/config_test.go @@ -28,16 +28,19 @@ func TestConfigApply(t *testing.T) { ProjectID: null.NewInt(1, true), Name: null.NewString("Name", true), Host: null.NewString("Host", true), + Timeout: types.NewNullDuration(5*time.Second, true), LogsTailURL: null.NewString("LogsTailURL", true), PushRefID: null.NewString("PushRefID", true), WebAppURL: null.NewString("foo", true), NoCompress: null.NewBool(true, true), StopOnError: null.NewBool(true, true), - Timeout: types.NewNullDuration(5*time.Second, true), APIVersion: null.NewInt(2, true), MaxMetricSamplesPerPackage: null.NewInt(2, true), MetricPushInterval: types.NewNullDuration(1*time.Second, true), MetricPushConcurrency: null.NewInt(3, true), + TracesEnabled: null.NewBool(true, true), + TracesHost: null.NewString("TracesHost", true), + TracesPushInterval: types.NewNullDuration(1*time.Second, true), AggregationPeriod: types.NewNullDuration(2*time.Second, true), AggregationCalcInterval: types.NewNullDuration(3*time.Second, true), AggregationWaitPeriod: types.NewNullDuration(4*time.Second, true), diff --git a/cloudapi/insights/client.go b/cloudapi/insights/client.go new file mode 100644 index 00000000000..dd6e3be4cf7 --- /dev/null +++ b/cloudapi/insights/client.go @@ -0,0 +1,207 @@ +package insights + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net" + "sync" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" + + "go.k6.io/k6/cloudapi/insights/proto/v1/ingester" +) + +const ( + testRunIDHeader = "X-K6TestRun-Id" + authorizationHeader = "Authorization" +) + +var ( + // ErrClientAlreadyInitialized is returned when the client is already initialized. + ErrClientAlreadyInitialized = errors.New("insights client already initialized") + + // ErrClientClosed is returned when the client is closed. + ErrClientClosed = errors.New("insights client closed") +) + +// ClientConfig is the configuration for the client. +type ClientConfig struct { + IngesterHost string + ConnectConfig ClientConnectConfig + AuthConfig ClientAuthConfig + TLSConfig ClientTLSConfig +} + +// ClientConnectConfig is the configuration for the client connection. +type ClientConnectConfig struct { + Block bool + FailOnNonTempDialError bool + Dialer func(context.Context, string) (net.Conn, error) +} + +// ClientAuthConfig is the configuration for the client authentication. +type ClientAuthConfig struct { + Enabled bool + TestRunID int64 + Token string + RequireTransportSecurity bool +} + +// ClientTLSConfig is the configuration for the client TLS. +type ClientTLSConfig struct { + Insecure bool + CertFile string +} + +// Client is the client for the k6 Insights ingester service. +type Client struct { + cfg ClientConfig + client ingester.IngesterServiceClient + conn *grpc.ClientConn + connMu *sync.RWMutex +} + +// NewClient creates a new client. +func NewClient(cfg ClientConfig) *Client { + return &Client{ + cfg: cfg, + client: nil, + conn: nil, + connMu: &sync.RWMutex{}, + } +} + +// Dial creates a client connection using ClientConfig. +func (c *Client) Dial(ctx context.Context) error { + c.connMu.Lock() + defer c.connMu.Unlock() + + if c.conn != nil { + return ErrClientAlreadyInitialized + } + + opts, err := dialOptionsFromClientConfig(c.cfg) + if err != nil { + return fmt.Errorf("failed to create dial options: %w", err) + } + + conn, err := grpc.DialContext(ctx, c.cfg.IngesterHost, opts...) + if err != nil { + return fmt.Errorf("failed to dial: %w", err) + } + + c.client = ingester.NewIngesterServiceClient(conn) + c.conn = conn + + return nil +} + +// IngestRequestMetadatasBatch ingests a batch of request metadatas. +func (c *Client) IngestRequestMetadatasBatch(ctx context.Context, requestMetadatas RequestMetadatas) error { + c.connMu.RLock() + closed := c.conn == nil + c.connMu.RUnlock() + if closed { + return ErrClientClosed + } + + if len(requestMetadatas) < 1 { + return nil + } + + req, err := newBatchCreateRequestMetadatasRequest(requestMetadatas) + if err != nil { + return fmt.Errorf("failed to create request from request metadatas: %w", err) + } + + // TODO(lukasz, retry-support): Retry request with returned metadatas. + // + // Note: There is currently no backend support backing up this retry mechanism. + _, err = c.client.BatchCreateRequestMetadatas(ctx, req) + if err != nil { + st := status.Convert(err) + return fmt.Errorf("failed to ingest request metadatas batch: code=%s, msg=%s", st.Code().String(), st.Message()) + } + + return nil +} + +// Close closes the client. +func (c *Client) Close() error { + c.connMu.Lock() + defer c.connMu.Unlock() + + if c.conn == nil { + return ErrClientClosed + } + + conn := c.conn + c.client = nil + c.conn = nil + + return conn.Close() +} + +func dialOptionsFromClientConfig(cfg ClientConfig) ([]grpc.DialOption, error) { + var opts []grpc.DialOption + + if cfg.ConnectConfig.Block { + opts = append(opts, grpc.WithBlock()) + } + + if cfg.ConnectConfig.FailOnNonTempDialError { + opts = append(opts, grpc.FailOnNonTempDialError(true)) + } + + if cfg.ConnectConfig.Dialer != nil { + opts = append(opts, grpc.WithContextDialer(cfg.ConnectConfig.Dialer)) + } + + if cfg.TLSConfig.Insecure { //nolint: nestif + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + } else { + if cfg.TLSConfig.CertFile != "" { + creds, err := credentials.NewClientTLSFromFile(cfg.TLSConfig.CertFile, "") + if err != nil { + return nil, fmt.Errorf("failed to load TLS credentials from file: %w", err) + } + opts = append(opts, grpc.WithTransportCredentials(creds)) + } else { + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS13}))) + } + } + + if cfg.AuthConfig.Enabled { + opts = append(opts, grpc.WithPerRPCCredentials(newPerRPCCredentials(cfg.AuthConfig))) + } + + return opts, nil +} + +type perRPCCredentials struct { + metadata map[string]string + requireTransportSecurity bool +} + +func newPerRPCCredentials(cfg ClientAuthConfig) perRPCCredentials { + return perRPCCredentials{ + metadata: map[string]string{ + testRunIDHeader: fmt.Sprintf("%d", cfg.TestRunID), + authorizationHeader: fmt.Sprintf("Token %s", cfg.Token), + }, + requireTransportSecurity: cfg.RequireTransportSecurity, + } +} + +func (c perRPCCredentials) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { + return c.metadata, nil +} + +func (c perRPCCredentials) RequireTransportSecurity() bool { + return c.requireTransportSecurity +} diff --git a/cloudapi/insights/client_test.go b/cloudapi/insights/client_test.go new file mode 100644 index 00000000000..9107e051731 --- /dev/null +++ b/cloudapi/insights/client_test.go @@ -0,0 +1,342 @@ +package insights + +import ( + "context" + "errors" + "net" + "testing" + "time" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/test/bufconn" + + "go.k6.io/k6/cloudapi/insights/proto/v1/ingester" +) + +func newMockListener(t *testing.T, ingesterServer ingester.IngesterServiceServer) *bufconn.Listener { + t.Helper() + + const size = 1024 * 1024 + l := bufconn.Listen(size) + t.Cleanup(func() { _ = l.Close() }) + + s := grpc.NewServer() + ingester.RegisterIngesterServiceServer(s, ingesterServer) + go func() { _ = s.Serve(l) }() + t.Cleanup(func() { s.GracefulStop() }) + + return l +} + +func newMockContextDialer(t *testing.T, l *bufconn.Listener) func(context.Context, string) (net.Conn, error) { + t.Helper() + + return func(ctx context.Context, _ string) (net.Conn, error) { + return l.DialContext(ctx) + } +} + +type mockWorkingIngesterServer struct { + ingester.UnimplementedIngesterServiceServer + batchCreateRequestMetadatasInvoked bool + dataUploaded bool +} + +func (s *mockWorkingIngesterServer) BatchCreateRequestMetadatas(ctx context.Context, _ *ingester.BatchCreateRequestMetadatasRequest) (*ingester.BatchCreateRequestMetadatasResponse, error) { + s.batchCreateRequestMetadatasInvoked = true + + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + s.dataUploaded = true + + return &ingester.BatchCreateRequestMetadatasResponse{ + RequestMetadatas: nil, + }, nil +} + +type mockFailingIngesterServer struct { + ingester.UnimplementedIngesterServiceServer + err error +} + +func (m *mockFailingIngesterServer) BatchCreateRequestMetadatas(_ context.Context, _ *ingester.BatchCreateRequestMetadatasRequest) (*ingester.BatchCreateRequestMetadatasResponse, error) { + return nil, m.err +} + +type fatalError struct{} + +func (*fatalError) Error() string { return "context dialer error" } +func (*fatalError) Temporary() bool { return false } + +func TestClient_Dial_ReturnsNoErrorWithWorkingDialer(t *testing.T) { + t.Parallel() + + // Given + ser := &mockWorkingIngesterServer{} + lis := newMockListener(t, ser) + + cfg := ClientConfig{ + ConnectConfig: ClientConnectConfig{Dialer: newMockContextDialer(t, lis)}, + TLSConfig: ClientTLSConfig{Insecure: true}, + } + cli := NewClient(cfg) + + // When + err := cli.Dial(context.Background()) + + // Then + require.NoError(t, err) +} + +func TestClient_Dial_ReturnsErrorWhenCalledTwice(t *testing.T) { + t.Parallel() + + // Given + ser := &mockWorkingIngesterServer{} + lis := newMockListener(t, ser) + + cfg := ClientConfig{ + ConnectConfig: ClientConnectConfig{Dialer: newMockContextDialer(t, lis)}, + TLSConfig: ClientTLSConfig{Insecure: true}, + } + cli := NewClient(cfg) + + // When + noErr := cli.Dial(context.Background()) + err := cli.Dial(context.Background()) + + // Then + require.NoError(t, noErr) + require.ErrorIs(t, err, ErrClientAlreadyInitialized) +} + +func TestClient_Dial_ReturnsNoErrorWithFailingDialer(t *testing.T) { + t.Parallel() + + // Given + cfg := ClientConfig{ + ConnectConfig: ClientConnectConfig{ + Block: true, + FailOnNonTempDialError: true, + Dialer: func(ctx context.Context, s string) (net.Conn, error) { + return nil, &fatalError{} + }, + }, + TLSConfig: ClientTLSConfig{Insecure: true}, + } + cli := NewClient(cfg) + + // When + err := cli.Dial(context.Background()) + + // Then + var fatalErr *fatalError + require.ErrorAs(t, err, &fatalErr) +} + +func TestClient_IngestRequestMetadatasBatch_ReturnsNoErrorWithWorkingServerAndNonCancelledContextAndNoData(t *testing.T) { + t.Parallel() + + // Given + ser := &mockWorkingIngesterServer{} + lis := newMockListener(t, ser) + + cfg := ClientConfig{ + ConnectConfig: ClientConnectConfig{Dialer: newMockContextDialer(t, lis)}, + TLSConfig: ClientTLSConfig{Insecure: true}, + } + cli := NewClient(cfg) + require.NoError(t, cli.Dial(context.Background())) + + // When + err := cli.IngestRequestMetadatasBatch(context.Background(), nil) + + // Then + require.NoError(t, err) + require.False(t, ser.batchCreateRequestMetadatasInvoked) + require.False(t, ser.dataUploaded) +} + +func TestClient_IngestRequestMetadatasBatch_ReturnsNoErrorWithWorkingServerAndNonCancelledContextAndData(t *testing.T) { + t.Parallel() + + // Given + ser := &mockWorkingIngesterServer{} + lis := newMockListener(t, ser) + + cfg := ClientConfig{ + ConnectConfig: ClientConnectConfig{Dialer: newMockContextDialer(t, lis)}, + TLSConfig: ClientTLSConfig{Insecure: true}, + } + cli := NewClient(cfg) + require.NoError(t, cli.Dial(context.Background())) + data := RequestMetadatas{ + { + TraceID: "test-trace-id-1", + Start: time.Unix(9, 0), + End: time.Unix(10, 0), + TestRunLabels: TestRunLabels{ID: 1337, Scenario: "test-scenario-1", Group: "test-group-1"}, + ProtocolLabels: ProtocolHTTPLabels{URL: "test-url-1", Method: "test-method-1", StatusCode: 200}, + }, + { + TraceID: "test-trace-id-2", + Start: time.Unix(19, 0), + End: time.Unix(20, 0), + TestRunLabels: TestRunLabels{ID: 1337, Scenario: "test-scenario-2", Group: "test-group-2"}, + ProtocolLabels: ProtocolHTTPLabels{URL: "test-url-2", Method: "test-method-2", StatusCode: 401}, + }, + } + + // When + err := cli.IngestRequestMetadatasBatch(context.Background(), data) + + // Then + require.NoError(t, err) + require.True(t, ser.batchCreateRequestMetadatasInvoked) + require.True(t, ser.dataUploaded) +} + +func TestClient_IngestRequestMetadatasBatch_ReturnsErrorWithWorkingServerAndCancelledContext(t *testing.T) { + t.Parallel() + + // Given + ser := &mockWorkingIngesterServer{} + lis := newMockListener(t, ser) + + cfg := ClientConfig{ + ConnectConfig: ClientConnectConfig{Dialer: newMockContextDialer(t, lis)}, + TLSConfig: ClientTLSConfig{Insecure: true}, + } + cli := NewClient(cfg) + require.NoError(t, cli.Dial(context.Background())) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + data := RequestMetadatas{ + { + TraceID: "test-trace-id-1", + Start: time.Unix(9, 0), + End: time.Unix(10, 0), + TestRunLabels: TestRunLabels{ID: 1337, Scenario: "test-scenario-1", Group: "test-group-1"}, + ProtocolLabels: ProtocolHTTPLabels{URL: "test-url-1", Method: "test-method-1", StatusCode: 200}, + }, + } + + // When + err := cli.IngestRequestMetadatasBatch(ctx, data) + + // Then + require.Error(t, err) + require.False(t, ser.batchCreateRequestMetadatasInvoked) + require.False(t, ser.dataUploaded) +} + +func TestClient_IngestRequestMetadatasBatch_ReturnsErrorWithUninitializedClient(t *testing.T) { + t.Parallel() + + // Given + ser := &mockWorkingIngesterServer{} + lis := newMockListener(t, ser) + + cfg := ClientConfig{ + ConnectConfig: ClientConnectConfig{Dialer: newMockContextDialer(t, lis)}, + TLSConfig: ClientTLSConfig{Insecure: true}, + } + cli := NewClient(cfg) + data := RequestMetadatas{ + { + TraceID: "test-trace-id-1", + Start: time.Unix(9, 0), + End: time.Unix(10, 0), + TestRunLabels: TestRunLabels{ID: 1337, Scenario: "test-scenario-1", Group: "test-group-1"}, + ProtocolLabels: ProtocolHTTPLabels{URL: "test-url-1", Method: "test-method-1", StatusCode: 200}, + }, + } + + // When + err := cli.IngestRequestMetadatasBatch(context.Background(), data) + + // Then + require.ErrorIs(t, err, ErrClientClosed) + require.False(t, ser.batchCreateRequestMetadatasInvoked) + require.False(t, ser.dataUploaded) +} + +func TestClient_IngestRequestMetadatasBatch_ReturnsErrorWithFailingServerAndNonCancelledContext(t *testing.T) { + t.Parallel() + + // Given + testErr := errors.New("test error") + ser := &mockFailingIngesterServer{err: testErr} + lis := newMockListener(t, ser) + + cfg := ClientConfig{ + ConnectConfig: ClientConnectConfig{Dialer: newMockContextDialer(t, lis)}, + TLSConfig: ClientTLSConfig{Insecure: true}, + } + cli := NewClient(cfg) + require.NoError(t, cli.Dial(context.Background())) + data := RequestMetadatas{ + { + TraceID: "test-trace-id-1", + Start: time.Unix(9, 0), + End: time.Unix(10, 0), + TestRunLabels: TestRunLabels{ID: 1337, Scenario: "test-scenario-1", Group: "test-group-1"}, + ProtocolLabels: ProtocolHTTPLabels{URL: "test-url-1", Method: "test-method-1", StatusCode: 200}, + }, + } + + // When + err := cli.IngestRequestMetadatasBatch(context.Background(), data) + + // Then + require.ErrorContains(t, err, testErr.Error()) +} + +func TestClient_Close_ReturnsNoErrorWhenClosedOnce(t *testing.T) { + t.Parallel() + + // Given + ser := &mockWorkingIngesterServer{} + lis := newMockListener(t, ser) + + cfg := ClientConfig{ + ConnectConfig: ClientConnectConfig{Dialer: newMockContextDialer(t, lis)}, + TLSConfig: ClientTLSConfig{Insecure: true}, + } + cli := NewClient(cfg) + require.NoError(t, cli.Dial(context.Background())) + + // When + err := cli.Close() + + // Then + require.NoError(t, err) +} + +func TestClient_Close_ReturnsNoErrorWhenClosedTwice(t *testing.T) { + t.Parallel() + + // Given + ser := &mockWorkingIngesterServer{} + lis := newMockListener(t, ser) + + cfg := ClientConfig{ + ConnectConfig: ClientConnectConfig{Dialer: newMockContextDialer(t, lis)}, + TLSConfig: ClientTLSConfig{Insecure: true}, + } + cli := NewClient(cfg) + require.NoError(t, cli.Dial(context.Background())) + + // When + noErr := cli.Close() + err := cli.Close() + + // Then + require.NoError(t, noErr) + require.ErrorIs(t, err, ErrClientClosed) +} diff --git a/cloudapi/insights/doc.go b/cloudapi/insights/doc.go new file mode 100644 index 00000000000..27a4c4880a3 --- /dev/null +++ b/cloudapi/insights/doc.go @@ -0,0 +1,3 @@ +// Package insights contains the domain data structures and client logic +// for the k6 cloud Insights API. +package insights diff --git a/cloudapi/insights/domain.go b/cloudapi/insights/domain.go new file mode 100644 index 00000000000..122e2beaad5 --- /dev/null +++ b/cloudapi/insights/domain.go @@ -0,0 +1,41 @@ +package insights + +import ( + "time" +) + +// TestRunLabels describes labels associated with a single test run. +type TestRunLabels struct { + ID int64 + Scenario string + Group string +} + +// ProtocolLabels is a dummy interface that is used for compile-time type checking. +type ProtocolLabels interface { + IsProtocolLabels() +} + +// ProtocolHTTPLabels describes labels associated with a single HTTP request. +type ProtocolHTTPLabels struct { + URL string + Method string + StatusCode int64 +} + +// IsProtocolLabels is a dummy implementation to satisfy the ProtocolLabels interface. +func (ProtocolHTTPLabels) IsProtocolLabels() { + // Do nothing +} + +// RequestMetadatas is a slice of RequestMetadata. +type RequestMetadatas []RequestMetadata + +// RequestMetadata describes metadata associated with a single *traced* request. +type RequestMetadata struct { + TraceID string + Start time.Time + End time.Time + TestRunLabels TestRunLabels + ProtocolLabels ProtocolLabels +} diff --git a/cloudapi/insights/mappers.go b/cloudapi/insights/mappers.go new file mode 100644 index 00000000000..f83329407cd --- /dev/null +++ b/cloudapi/insights/mappers.go @@ -0,0 +1,67 @@ +package insights + +import ( + "errors" + "fmt" + + "go.k6.io/k6/cloudapi/insights/proto/v1/ingester" + "go.k6.io/k6/cloudapi/insights/proto/v1/k6" +) + +func newBatchCreateRequestMetadatasRequest( + requestMetadatas RequestMetadatas, +) (*ingester.BatchCreateRequestMetadatasRequest, error) { + reqs := make([]*ingester.CreateRequestMetadataRequest, 0, len(requestMetadatas)) + for _, rm := range requestMetadatas { + req, err := newCreateRequestMetadataRequest(rm) + if err != nil { + return nil, fmt.Errorf("failed to create request metadata request: %w", err) + } + + reqs = append(reqs, req) + } + + return &ingester.BatchCreateRequestMetadatasRequest{ + Requests: reqs, + }, nil +} + +func newCreateRequestMetadataRequest(requestMetadata RequestMetadata) (*ingester.CreateRequestMetadataRequest, error) { + rm := &k6.RequestMetadata{ + TraceID: requestMetadata.TraceID, + StartTimeUnixNano: requestMetadata.Start.UnixNano(), + EndTimeUnixNano: requestMetadata.End.UnixNano(), + TestRunLabels: &k6.TestRunLabels{ + ID: requestMetadata.TestRunLabels.ID, + Scenario: requestMetadata.TestRunLabels.Scenario, + Group: requestMetadata.TestRunLabels.Group, + }, + ProtocolLabels: nil, + } + + if err := setProtocolLabels(rm, requestMetadata.ProtocolLabels); err != nil { + return nil, fmt.Errorf("failed to set protocol labels: %w", err) + } + + return &ingester.CreateRequestMetadataRequest{ + RequestMetadata: rm, + }, nil +} + +func setProtocolLabels(rm *k6.RequestMetadata, labels ProtocolLabels) error { + // TODO(lukasz, other-proto-support): Set other protocol labels. + switch l := labels.(type) { + case ProtocolHTTPLabels: + rm.ProtocolLabels = &k6.RequestMetadata_HTTPLabels{ + HTTPLabels: &k6.HTTPLabels{ + Url: l.URL, + Method: l.Method, + StatusCode: l.StatusCode, + }, + } + default: + return errors.New("unknown protocol labels type") + } + + return nil +} diff --git a/cloudapi/insights/mappers_test.go b/cloudapi/insights/mappers_test.go new file mode 100644 index 00000000000..d7f08c259ed --- /dev/null +++ b/cloudapi/insights/mappers_test.go @@ -0,0 +1,70 @@ +package insights + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "go.k6.io/k6/cloudapi/insights/proto/v1/ingester" + "go.k6.io/k6/cloudapi/insights/proto/v1/k6" +) + +func Test_newBatchCreateRequestMetadatasRequest_CorrectlyMapsDomainTypeToProtoDefinition(t *testing.T) { + t.Parallel() + + // Given + rms := RequestMetadatas{ + { + TraceID: "test-trace-id-1", + Start: time.Unix(9, 0), + End: time.Unix(10, 0), + TestRunLabels: TestRunLabels{ID: 1337, Scenario: "test-scenario-1", Group: "test-group-1"}, + ProtocolLabels: ProtocolHTTPLabels{URL: "test-url-1", Method: "test-method-1", StatusCode: 200}, + }, + { + TraceID: "test-trace-id-2", + Start: time.Unix(19, 0), + End: time.Unix(20, 0), + TestRunLabels: TestRunLabels{ID: 1337, Scenario: "test-scenario-2", Group: "test-group-2"}, + ProtocolLabels: ProtocolHTTPLabels{URL: "test-url-2", Method: "test-method-2", StatusCode: 401}, + }, + } + + // When + got, err := newBatchCreateRequestMetadatasRequest(rms) + + // Then + expected := []*ingester.CreateRequestMetadataRequest{ + { + RequestMetadata: &k6.RequestMetadata{ + TraceID: "test-trace-id-1", + StartTimeUnixNano: time.Unix(9, 0).UnixNano(), + EndTimeUnixNano: time.Unix(10, 0).UnixNano(), + TestRunLabels: &k6.TestRunLabels{ID: 1337, Scenario: "test-scenario-1", Group: "test-group-1"}, + ProtocolLabels: &k6.RequestMetadata_HTTPLabels{ + HTTPLabels: &k6.HTTPLabels{ + Url: "test-url-1", Method: "test-method-1", StatusCode: 200, + }, + }, + }, + }, + { + RequestMetadata: &k6.RequestMetadata{ + TraceID: "test-trace-id-2", + StartTimeUnixNano: time.Unix(19, 0).UnixNano(), + EndTimeUnixNano: time.Unix(20, 0).UnixNano(), + TestRunLabels: &k6.TestRunLabels{ID: 1337, Scenario: "test-scenario-2", Group: "test-group-2"}, + ProtocolLabels: &k6.RequestMetadata_HTTPLabels{ + HTTPLabels: &k6.HTTPLabels{ + Url: "test-url-2", Method: "test-method-2", StatusCode: 401, + }, + }, + }, + }, + } + + require.NoError(t, err) + require.Len(t, got.Requests, 2) + require.ElementsMatch(t, got.Requests, expected) +} diff --git a/cloudapi/insights/proto/gen.go b/cloudapi/insights/proto/gen.go new file mode 100644 index 00000000000..7980b3c6d3e --- /dev/null +++ b/cloudapi/insights/proto/gen.go @@ -0,0 +1,11 @@ +// Package proto contains the Protobuf definitions used +// by the k6 Insights. +package proto + +//nolint:lll +//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./v1/common/common.proto +//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./v1/ingester/ingester.proto +//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./v1/k6/labels.proto +//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./v1/k6/request_metadata.proto +//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./v1/trace/labels.proto +//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./v1/trace/span.proto diff --git a/cloudapi/insights/proto/v1/common/common.pb.go b/cloudapi/insights/proto/v1/common/common.pb.go new file mode 100644 index 00000000000..b68fb4b4a6a --- /dev/null +++ b/cloudapi/insights/proto/v1/common/common.pb.go @@ -0,0 +1,510 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v3.21.12 +// source: v1/common/common.proto + +package common + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Messages below are copied from https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/common/v1/common.proto +// AnyValue is used to represent any type of attribute value. AnyValue may contain a +// primitive value such as a string or integer or it may contain an arbitrary nested +// object containing arrays, key-value lists and primitives. +type AnyValue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The value is one of the listed fields. It is valid for all values to be unspecified + // in which case this AnyValue is considered to be "empty". + // + // Types that are assignable to Value: + // + // *AnyValue_StringValue + // *AnyValue_BoolValue + // *AnyValue_IntValue + // *AnyValue_DoubleValue + // *AnyValue_ArrayValue + // *AnyValue_KvlistValue + // *AnyValue_BytesValue + Value isAnyValue_Value `protobuf_oneof:"value"` +} + +func (x *AnyValue) Reset() { + *x = AnyValue{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_common_common_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AnyValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AnyValue) ProtoMessage() {} + +func (x *AnyValue) ProtoReflect() protoreflect.Message { + mi := &file_v1_common_common_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AnyValue.ProtoReflect.Descriptor instead. +func (*AnyValue) Descriptor() ([]byte, []int) { + return file_v1_common_common_proto_rawDescGZIP(), []int{0} +} + +func (m *AnyValue) GetValue() isAnyValue_Value { + if m != nil { + return m.Value + } + return nil +} + +func (x *AnyValue) GetStringValue() string { + if x, ok := x.GetValue().(*AnyValue_StringValue); ok { + return x.StringValue + } + return "" +} + +func (x *AnyValue) GetBoolValue() bool { + if x, ok := x.GetValue().(*AnyValue_BoolValue); ok { + return x.BoolValue + } + return false +} + +func (x *AnyValue) GetIntValue() int64 { + if x, ok := x.GetValue().(*AnyValue_IntValue); ok { + return x.IntValue + } + return 0 +} + +func (x *AnyValue) GetDoubleValue() float64 { + if x, ok := x.GetValue().(*AnyValue_DoubleValue); ok { + return x.DoubleValue + } + return 0 +} + +func (x *AnyValue) GetArrayValue() *ArrayValue { + if x, ok := x.GetValue().(*AnyValue_ArrayValue); ok { + return x.ArrayValue + } + return nil +} + +func (x *AnyValue) GetKvlistValue() *KeyValueList { + if x, ok := x.GetValue().(*AnyValue_KvlistValue); ok { + return x.KvlistValue + } + return nil +} + +func (x *AnyValue) GetBytesValue() []byte { + if x, ok := x.GetValue().(*AnyValue_BytesValue); ok { + return x.BytesValue + } + return nil +} + +type isAnyValue_Value interface { + isAnyValue_Value() +} + +type AnyValue_StringValue struct { + StringValue string `protobuf:"bytes,1,opt,name=string_value,json=stringValue,proto3,oneof"` +} + +type AnyValue_BoolValue struct { + BoolValue bool `protobuf:"varint,2,opt,name=bool_value,json=boolValue,proto3,oneof"` +} + +type AnyValue_IntValue struct { + IntValue int64 `protobuf:"varint,3,opt,name=int_value,json=intValue,proto3,oneof"` +} + +type AnyValue_DoubleValue struct { + DoubleValue float64 `protobuf:"fixed64,4,opt,name=double_value,json=doubleValue,proto3,oneof"` +} + +type AnyValue_ArrayValue struct { + ArrayValue *ArrayValue `protobuf:"bytes,5,opt,name=array_value,json=arrayValue,proto3,oneof"` +} + +type AnyValue_KvlistValue struct { + KvlistValue *KeyValueList `protobuf:"bytes,6,opt,name=kvlist_value,json=kvlistValue,proto3,oneof"` +} + +type AnyValue_BytesValue struct { + BytesValue []byte `protobuf:"bytes,7,opt,name=bytes_value,json=bytesValue,proto3,oneof"` +} + +func (*AnyValue_StringValue) isAnyValue_Value() {} + +func (*AnyValue_BoolValue) isAnyValue_Value() {} + +func (*AnyValue_IntValue) isAnyValue_Value() {} + +func (*AnyValue_DoubleValue) isAnyValue_Value() {} + +func (*AnyValue_ArrayValue) isAnyValue_Value() {} + +func (*AnyValue_KvlistValue) isAnyValue_Value() {} + +func (*AnyValue_BytesValue) isAnyValue_Value() {} + +// ArrayValue is a list of AnyValue messages. We need ArrayValue as a message +// since oneof in AnyValue does not allow repeated fields. +type ArrayValue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Array of values. The array may be empty (contain 0 elements). + Values []*AnyValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` +} + +func (x *ArrayValue) Reset() { + *x = ArrayValue{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_common_common_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ArrayValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ArrayValue) ProtoMessage() {} + +func (x *ArrayValue) ProtoReflect() protoreflect.Message { + mi := &file_v1_common_common_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ArrayValue.ProtoReflect.Descriptor instead. +func (*ArrayValue) Descriptor() ([]byte, []int) { + return file_v1_common_common_proto_rawDescGZIP(), []int{1} +} + +func (x *ArrayValue) GetValues() []*AnyValue { + if x != nil { + return x.Values + } + return nil +} + +// KeyValueList is a list of KeyValue messages. We need KeyValueList as a message +// since `oneof` in AnyValue does not allow repeated fields. Everywhere else where we need +// a list of KeyValue messages (e.g. in Span) we use `repeated KeyValue` directly to +// avoid unnecessary extra wrapping (which slows down the protocol). The 2 approaches +// are semantically equivalent. +type KeyValueList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A collection of key/value pairs of key-value pairs. The list may be empty (may + // contain 0 elements). + // The keys MUST be unique (it is not allowed to have more than one + // value with the same key). + Values []*KeyValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` +} + +func (x *KeyValueList) Reset() { + *x = KeyValueList{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_common_common_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *KeyValueList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KeyValueList) ProtoMessage() {} + +func (x *KeyValueList) ProtoReflect() protoreflect.Message { + mi := &file_v1_common_common_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KeyValueList.ProtoReflect.Descriptor instead. +func (*KeyValueList) Descriptor() ([]byte, []int) { + return file_v1_common_common_proto_rawDescGZIP(), []int{2} +} + +func (x *KeyValueList) GetValues() []*KeyValue { + if x != nil { + return x.Values + } + return nil +} + +// KeyValue is a key-value pair that is used to store Span attributes, Link +// attributes, etc. +type KeyValue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value *AnyValue `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *KeyValue) Reset() { + *x = KeyValue{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_common_common_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *KeyValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KeyValue) ProtoMessage() {} + +func (x *KeyValue) ProtoReflect() protoreflect.Message { + mi := &file_v1_common_common_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KeyValue.ProtoReflect.Descriptor instead. +func (*KeyValue) Descriptor() ([]byte, []int) { + return file_v1_common_common_proto_rawDescGZIP(), []int{3} +} + +func (x *KeyValue) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *KeyValue) GetValue() *AnyValue { + if x != nil { + return x.Value + } + return nil +} + +var File_v1_common_common_proto protoreflect.FileDescriptor + +var file_v1_common_common_proto_rawDesc = []byte{ + 0x0a, 0x16, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x21, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x22, 0xe8, 0x02, 0x0a, 0x08, + 0x41, 0x6e, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, + 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1f, 0x0a, + 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, + 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, + 0x0c, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x50, 0x0a, 0x0b, 0x61, 0x72, 0x72, 0x61, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x41, 0x72, 0x72, 0x61, + 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x72, 0x72, 0x61, 0x79, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x54, 0x0a, 0x0c, 0x6b, 0x76, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6b, 0x36, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4b, + 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6b, + 0x76, 0x6c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, 0x0a, 0x0b, 0x62, 0x79, + 0x74, 0x65, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x48, + 0x00, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x07, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x51, 0x0a, 0x0a, 0x41, 0x72, 0x72, 0x61, 0x79, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, + 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, + 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x41, 0x6e, 0x79, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x53, 0x0a, 0x0c, 0x4b, 0x65, 0x79, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x43, 0x0a, 0x06, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6b, 0x36, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4b, 0x65, + 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x5f, + 0x0a, 0x08, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x41, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6b, 0x36, + 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x41, 0x6e, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, + 0x2f, 0x5a, 0x2d, 0x67, 0x6f, 0x2e, 0x6b, 0x36, 0x2e, 0x69, 0x6f, 0x2f, 0x6b, 0x36, 0x2f, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x61, 0x70, 0x69, 0x2f, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_v1_common_common_proto_rawDescOnce sync.Once + file_v1_common_common_proto_rawDescData = file_v1_common_common_proto_rawDesc +) + +func file_v1_common_common_proto_rawDescGZIP() []byte { + file_v1_common_common_proto_rawDescOnce.Do(func() { + file_v1_common_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_v1_common_common_proto_rawDescData) + }) + return file_v1_common_common_proto_rawDescData +} + +var file_v1_common_common_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_v1_common_common_proto_goTypes = []interface{}{ + (*AnyValue)(nil), // 0: k6.cloud.insights.proto.v1.common.AnyValue + (*ArrayValue)(nil), // 1: k6.cloud.insights.proto.v1.common.ArrayValue + (*KeyValueList)(nil), // 2: k6.cloud.insights.proto.v1.common.KeyValueList + (*KeyValue)(nil), // 3: k6.cloud.insights.proto.v1.common.KeyValue +} +var file_v1_common_common_proto_depIdxs = []int32{ + 1, // 0: k6.cloud.insights.proto.v1.common.AnyValue.array_value:type_name -> k6.cloud.insights.proto.v1.common.ArrayValue + 2, // 1: k6.cloud.insights.proto.v1.common.AnyValue.kvlist_value:type_name -> k6.cloud.insights.proto.v1.common.KeyValueList + 0, // 2: k6.cloud.insights.proto.v1.common.ArrayValue.values:type_name -> k6.cloud.insights.proto.v1.common.AnyValue + 3, // 3: k6.cloud.insights.proto.v1.common.KeyValueList.values:type_name -> k6.cloud.insights.proto.v1.common.KeyValue + 0, // 4: k6.cloud.insights.proto.v1.common.KeyValue.value:type_name -> k6.cloud.insights.proto.v1.common.AnyValue + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_v1_common_common_proto_init() } +func file_v1_common_common_proto_init() { + if File_v1_common_common_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_v1_common_common_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AnyValue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_common_common_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ArrayValue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_common_common_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyValueList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_common_common_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyValue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_v1_common_common_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*AnyValue_StringValue)(nil), + (*AnyValue_BoolValue)(nil), + (*AnyValue_IntValue)(nil), + (*AnyValue_DoubleValue)(nil), + (*AnyValue_ArrayValue)(nil), + (*AnyValue_KvlistValue)(nil), + (*AnyValue_BytesValue)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_v1_common_common_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_v1_common_common_proto_goTypes, + DependencyIndexes: file_v1_common_common_proto_depIdxs, + MessageInfos: file_v1_common_common_proto_msgTypes, + }.Build() + File_v1_common_common_proto = out.File + file_v1_common_common_proto_rawDesc = nil + file_v1_common_common_proto_goTypes = nil + file_v1_common_common_proto_depIdxs = nil +} diff --git a/cloudapi/insights/proto/v1/common/common.proto b/cloudapi/insights/proto/v1/common/common.proto new file mode 100644 index 00000000000..09584d382a9 --- /dev/null +++ b/cloudapi/insights/proto/v1/common/common.proto @@ -0,0 +1,65 @@ +// Copyright 2019, OpenTelemetry Authors +// +// 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. + +// Messages below are copied from https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/common/v1/common.proto + +syntax = "proto3"; + +package k6.cloud.insights.proto.v1.common; + +option go_package = "go.k6.io/k6/cloudapi/insights/proto/v1/common"; + +// AnyValue is used to represent any type of attribute value. AnyValue may contain a +// primitive value such as a string or integer or it may contain an arbitrary nested +// object containing arrays, key-value lists and primitives. +message AnyValue { + // The value is one of the listed fields. It is valid for all values to be unspecified + // in which case this AnyValue is considered to be "empty". + oneof value { + string string_value = 1; + bool bool_value = 2; + int64 int_value = 3; + double double_value = 4; + ArrayValue array_value = 5; + KeyValueList kvlist_value = 6; + bytes bytes_value = 7; + } +} + +// ArrayValue is a list of AnyValue messages. We need ArrayValue as a message +// since oneof in AnyValue does not allow repeated fields. +message ArrayValue { + // Array of values. The array may be empty (contain 0 elements). + repeated AnyValue values = 1; +} + +// KeyValueList is a list of KeyValue messages. We need KeyValueList as a message +// since `oneof` in AnyValue does not allow repeated fields. Everywhere else where we need +// a list of KeyValue messages (e.g. in Span) we use `repeated KeyValue` directly to +// avoid unnecessary extra wrapping (which slows down the protocol). The 2 approaches +// are semantically equivalent. +message KeyValueList { + // A collection of key/value pairs of key-value pairs. The list may be empty (may + // contain 0 elements). + // The keys MUST be unique (it is not allowed to have more than one + // value with the same key). + repeated KeyValue values = 1; +} + +// KeyValue is a key-value pair that is used to store Span attributes, Link +// attributes, etc. +message KeyValue { + string key = 1; + AnyValue value = 2; +} diff --git a/cloudapi/insights/proto/v1/ingester/ingester.pb.go b/cloudapi/insights/proto/v1/ingester/ingester.pb.go new file mode 100644 index 00000000000..0c5a5e6cb4e --- /dev/null +++ b/cloudapi/insights/proto/v1/ingester/ingester.pb.go @@ -0,0 +1,525 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v3.21.12 +// source: v1/ingester/ingester.proto + +package ingester + +import ( + k6 "go.k6.io/k6/cloudapi/insights/proto/v1/k6" + trace "go.k6.io/k6/cloudapi/insights/proto/v1/trace" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type BatchCreateRequestMetadatasRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Requests []*CreateRequestMetadataRequest `protobuf:"bytes,1,rep,name=Requests,proto3" json:"Requests,omitempty"` +} + +func (x *BatchCreateRequestMetadatasRequest) Reset() { + *x = BatchCreateRequestMetadatasRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_ingester_ingester_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BatchCreateRequestMetadatasRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchCreateRequestMetadatasRequest) ProtoMessage() {} + +func (x *BatchCreateRequestMetadatasRequest) ProtoReflect() protoreflect.Message { + mi := &file_v1_ingester_ingester_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BatchCreateRequestMetadatasRequest.ProtoReflect.Descriptor instead. +func (*BatchCreateRequestMetadatasRequest) Descriptor() ([]byte, []int) { + return file_v1_ingester_ingester_proto_rawDescGZIP(), []int{0} +} + +func (x *BatchCreateRequestMetadatasRequest) GetRequests() []*CreateRequestMetadataRequest { + if x != nil { + return x.Requests + } + return nil +} + +type CreateRequestMetadataRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestMetadata *k6.RequestMetadata `protobuf:"bytes,1,opt,name=RequestMetadata,proto3" json:"RequestMetadata,omitempty"` +} + +func (x *CreateRequestMetadataRequest) Reset() { + *x = CreateRequestMetadataRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_ingester_ingester_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateRequestMetadataRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateRequestMetadataRequest) ProtoMessage() {} + +func (x *CreateRequestMetadataRequest) ProtoReflect() protoreflect.Message { + mi := &file_v1_ingester_ingester_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateRequestMetadataRequest.ProtoReflect.Descriptor instead. +func (*CreateRequestMetadataRequest) Descriptor() ([]byte, []int) { + return file_v1_ingester_ingester_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateRequestMetadataRequest) GetRequestMetadata() *k6.RequestMetadata { + if x != nil { + return x.RequestMetadata + } + return nil +} + +type BatchCreateRequestMetadatasResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequestMetadatas []*k6.RequestMetadata `protobuf:"bytes,1,rep,name=RequestMetadatas,proto3" json:"RequestMetadatas,omitempty"` +} + +func (x *BatchCreateRequestMetadatasResponse) Reset() { + *x = BatchCreateRequestMetadatasResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_ingester_ingester_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BatchCreateRequestMetadatasResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchCreateRequestMetadatasResponse) ProtoMessage() {} + +func (x *BatchCreateRequestMetadatasResponse) ProtoReflect() protoreflect.Message { + mi := &file_v1_ingester_ingester_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BatchCreateRequestMetadatasResponse.ProtoReflect.Descriptor instead. +func (*BatchCreateRequestMetadatasResponse) Descriptor() ([]byte, []int) { + return file_v1_ingester_ingester_proto_rawDescGZIP(), []int{2} +} + +func (x *BatchCreateRequestMetadatasResponse) GetRequestMetadatas() []*k6.RequestMetadata { + if x != nil { + return x.RequestMetadatas + } + return nil +} + +type BatchCreateSpansRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Requests []*CreateSpanRequest `protobuf:"bytes,1,rep,name=Requests,proto3" json:"Requests,omitempty"` +} + +func (x *BatchCreateSpansRequest) Reset() { + *x = BatchCreateSpansRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_ingester_ingester_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BatchCreateSpansRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchCreateSpansRequest) ProtoMessage() {} + +func (x *BatchCreateSpansRequest) ProtoReflect() protoreflect.Message { + mi := &file_v1_ingester_ingester_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BatchCreateSpansRequest.ProtoReflect.Descriptor instead. +func (*BatchCreateSpansRequest) Descriptor() ([]byte, []int) { + return file_v1_ingester_ingester_proto_rawDescGZIP(), []int{3} +} + +func (x *BatchCreateSpansRequest) GetRequests() []*CreateSpanRequest { + if x != nil { + return x.Requests + } + return nil +} + +type CreateSpanRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Span *trace.Span `protobuf:"bytes,1,opt,name=Span,proto3" json:"Span,omitempty"` +} + +func (x *CreateSpanRequest) Reset() { + *x = CreateSpanRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_ingester_ingester_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateSpanRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateSpanRequest) ProtoMessage() {} + +func (x *CreateSpanRequest) ProtoReflect() protoreflect.Message { + mi := &file_v1_ingester_ingester_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateSpanRequest.ProtoReflect.Descriptor instead. +func (*CreateSpanRequest) Descriptor() ([]byte, []int) { + return file_v1_ingester_ingester_proto_rawDescGZIP(), []int{4} +} + +func (x *CreateSpanRequest) GetSpan() *trace.Span { + if x != nil { + return x.Span + } + return nil +} + +type BatchCreateSpansResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Spans []*trace.Span `protobuf:"bytes,1,rep,name=Spans,proto3" json:"Spans,omitempty"` +} + +func (x *BatchCreateSpansResponse) Reset() { + *x = BatchCreateSpansResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_ingester_ingester_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BatchCreateSpansResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchCreateSpansResponse) ProtoMessage() {} + +func (x *BatchCreateSpansResponse) ProtoReflect() protoreflect.Message { + mi := &file_v1_ingester_ingester_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BatchCreateSpansResponse.ProtoReflect.Descriptor instead. +func (*BatchCreateSpansResponse) Descriptor() ([]byte, []int) { + return file_v1_ingester_ingester_proto_rawDescGZIP(), []int{5} +} + +func (x *BatchCreateSpansResponse) GetSpans() []*trace.Span { + if x != nil { + return x.Spans + } + return nil +} + +var File_v1_ingester_ingester_proto protoreflect.FileDescriptor + +var file_v1_ingester_ingester_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x69, 0x6e, + 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x23, 0x6b, 0x36, + 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, + 0x72, 0x1a, 0x1c, 0x76, 0x31, 0x2f, 0x6b, 0x36, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x13, 0x76, 0x31, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x83, 0x01, 0x0a, 0x22, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5d, 0x0a, 0x08, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, + 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, + 0x74, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x52, 0x08, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x22, 0x78, 0x0a, 0x1c, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x58, 0x0a, 0x0f, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, + 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, + 0x2e, 0x6b, 0x36, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x52, 0x0f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x22, 0x81, 0x01, 0x0a, 0x23, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x10, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x76, 0x31, 0x2e, 0x6b, 0x36, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x10, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x73, 0x22, 0x6d, 0x0a, 0x17, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x52, 0x0a, 0x08, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x76, 0x31, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x08, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x22, 0x4f, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x04, + 0x53, 0x70, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6b, 0x36, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x53, 0x70, + 0x61, 0x6e, 0x52, 0x04, 0x53, 0x70, 0x61, 0x6e, 0x22, 0x58, 0x0a, 0x18, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x05, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, + 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, + 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x05, 0x53, 0x70, 0x61, + 0x6e, 0x73, 0x32, 0xda, 0x02, 0x0a, 0x0f, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xb2, 0x01, 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x73, 0x12, 0x47, 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x76, 0x31, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x48, 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, + 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x69, 0x6e, 0x67, + 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x91, 0x01, 0x0a, 0x10, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x73, + 0x12, 0x3c, 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, + 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x69, 0x6e, + 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3d, + 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, + 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x69, 0x6e, 0x67, 0x65, + 0x73, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x53, 0x70, 0x61, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, + 0x31, 0x5a, 0x2f, 0x67, 0x6f, 0x2e, 0x6b, 0x36, 0x2e, 0x69, 0x6f, 0x2f, 0x6b, 0x36, 0x2f, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x61, 0x70, 0x69, 0x2f, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, + 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_v1_ingester_ingester_proto_rawDescOnce sync.Once + file_v1_ingester_ingester_proto_rawDescData = file_v1_ingester_ingester_proto_rawDesc +) + +func file_v1_ingester_ingester_proto_rawDescGZIP() []byte { + file_v1_ingester_ingester_proto_rawDescOnce.Do(func() { + file_v1_ingester_ingester_proto_rawDescData = protoimpl.X.CompressGZIP(file_v1_ingester_ingester_proto_rawDescData) + }) + return file_v1_ingester_ingester_proto_rawDescData +} + +var file_v1_ingester_ingester_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_v1_ingester_ingester_proto_goTypes = []interface{}{ + (*BatchCreateRequestMetadatasRequest)(nil), // 0: k6.cloud.insights.proto.v1.ingester.BatchCreateRequestMetadatasRequest + (*CreateRequestMetadataRequest)(nil), // 1: k6.cloud.insights.proto.v1.ingester.CreateRequestMetadataRequest + (*BatchCreateRequestMetadatasResponse)(nil), // 2: k6.cloud.insights.proto.v1.ingester.BatchCreateRequestMetadatasResponse + (*BatchCreateSpansRequest)(nil), // 3: k6.cloud.insights.proto.v1.ingester.BatchCreateSpansRequest + (*CreateSpanRequest)(nil), // 4: k6.cloud.insights.proto.v1.ingester.CreateSpanRequest + (*BatchCreateSpansResponse)(nil), // 5: k6.cloud.insights.proto.v1.ingester.BatchCreateSpansResponse + (*k6.RequestMetadata)(nil), // 6: k6.cloud.insights.proto.v1.k6.RequestMetadata + (*trace.Span)(nil), // 7: k6.cloud.insights.proto.v1.trace.Span +} +var file_v1_ingester_ingester_proto_depIdxs = []int32{ + 1, // 0: k6.cloud.insights.proto.v1.ingester.BatchCreateRequestMetadatasRequest.Requests:type_name -> k6.cloud.insights.proto.v1.ingester.CreateRequestMetadataRequest + 6, // 1: k6.cloud.insights.proto.v1.ingester.CreateRequestMetadataRequest.RequestMetadata:type_name -> k6.cloud.insights.proto.v1.k6.RequestMetadata + 6, // 2: k6.cloud.insights.proto.v1.ingester.BatchCreateRequestMetadatasResponse.RequestMetadatas:type_name -> k6.cloud.insights.proto.v1.k6.RequestMetadata + 4, // 3: k6.cloud.insights.proto.v1.ingester.BatchCreateSpansRequest.Requests:type_name -> k6.cloud.insights.proto.v1.ingester.CreateSpanRequest + 7, // 4: k6.cloud.insights.proto.v1.ingester.CreateSpanRequest.Span:type_name -> k6.cloud.insights.proto.v1.trace.Span + 7, // 5: k6.cloud.insights.proto.v1.ingester.BatchCreateSpansResponse.Spans:type_name -> k6.cloud.insights.proto.v1.trace.Span + 0, // 6: k6.cloud.insights.proto.v1.ingester.IngesterService.BatchCreateRequestMetadatas:input_type -> k6.cloud.insights.proto.v1.ingester.BatchCreateRequestMetadatasRequest + 3, // 7: k6.cloud.insights.proto.v1.ingester.IngesterService.BatchCreateSpans:input_type -> k6.cloud.insights.proto.v1.ingester.BatchCreateSpansRequest + 2, // 8: k6.cloud.insights.proto.v1.ingester.IngesterService.BatchCreateRequestMetadatas:output_type -> k6.cloud.insights.proto.v1.ingester.BatchCreateRequestMetadatasResponse + 5, // 9: k6.cloud.insights.proto.v1.ingester.IngesterService.BatchCreateSpans:output_type -> k6.cloud.insights.proto.v1.ingester.BatchCreateSpansResponse + 8, // [8:10] is the sub-list for method output_type + 6, // [6:8] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_v1_ingester_ingester_proto_init() } +func file_v1_ingester_ingester_proto_init() { + if File_v1_ingester_ingester_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_v1_ingester_ingester_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BatchCreateRequestMetadatasRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_ingester_ingester_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateRequestMetadataRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_ingester_ingester_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BatchCreateRequestMetadatasResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_ingester_ingester_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BatchCreateSpansRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_ingester_ingester_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateSpanRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_ingester_ingester_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BatchCreateSpansResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_v1_ingester_ingester_proto_rawDesc, + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_v1_ingester_ingester_proto_goTypes, + DependencyIndexes: file_v1_ingester_ingester_proto_depIdxs, + MessageInfos: file_v1_ingester_ingester_proto_msgTypes, + }.Build() + File_v1_ingester_ingester_proto = out.File + file_v1_ingester_ingester_proto_rawDesc = nil + file_v1_ingester_ingester_proto_goTypes = nil + file_v1_ingester_ingester_proto_depIdxs = nil +} diff --git a/cloudapi/insights/proto/v1/ingester/ingester.proto b/cloudapi/insights/proto/v1/ingester/ingester.proto new file mode 100644 index 00000000000..70b03867b6b --- /dev/null +++ b/cloudapi/insights/proto/v1/ingester/ingester.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package k6.cloud.insights.proto.v1.ingester; + +option go_package = "go.k6.io/k6/cloudapi/insights/proto/v1/ingester"; + +import "v1/k6/request_metadata.proto"; +import "v1/trace/span.proto"; + +// The IngesterService contains the methods to ingest Spans and Request Metadata, +// which include information about a k6 load tests. +service IngesterService { + rpc BatchCreateRequestMetadatas(BatchCreateRequestMetadatasRequest) returns (BatchCreateRequestMetadatasResponse) {} + rpc BatchCreateSpans(BatchCreateSpansRequest) returns (BatchCreateSpansResponse) {} +} + +message BatchCreateRequestMetadatasRequest { + repeated CreateRequestMetadataRequest Requests = 1; +} + +message CreateRequestMetadataRequest { + k6.RequestMetadata RequestMetadata = 1; +} + +message BatchCreateRequestMetadatasResponse { + repeated k6.RequestMetadata RequestMetadatas = 1; +} + +message BatchCreateSpansRequest { + repeated CreateSpanRequest Requests = 1; +} + +message CreateSpanRequest { + trace.Span Span = 1; +} + +message BatchCreateSpansResponse { + repeated trace.Span Spans = 1; +} diff --git a/cloudapi/insights/proto/v1/ingester/ingester_grpc.pb.go b/cloudapi/insights/proto/v1/ingester/ingester_grpc.pb.go new file mode 100644 index 00000000000..608c60a2be4 --- /dev/null +++ b/cloudapi/insights/proto/v1/ingester/ingester_grpc.pb.go @@ -0,0 +1,146 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v3.21.12 +// source: v1/ingester/ingester.proto + +package ingester + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + IngesterService_BatchCreateRequestMetadatas_FullMethodName = "/k6.cloud.insights.proto.v1.ingester.IngesterService/BatchCreateRequestMetadatas" + IngesterService_BatchCreateSpans_FullMethodName = "/k6.cloud.insights.proto.v1.ingester.IngesterService/BatchCreateSpans" +) + +// IngesterServiceClient is the client API for IngesterService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type IngesterServiceClient interface { + BatchCreateRequestMetadatas(ctx context.Context, in *BatchCreateRequestMetadatasRequest, opts ...grpc.CallOption) (*BatchCreateRequestMetadatasResponse, error) + BatchCreateSpans(ctx context.Context, in *BatchCreateSpansRequest, opts ...grpc.CallOption) (*BatchCreateSpansResponse, error) +} + +type ingesterServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewIngesterServiceClient(cc grpc.ClientConnInterface) IngesterServiceClient { + return &ingesterServiceClient{cc} +} + +func (c *ingesterServiceClient) BatchCreateRequestMetadatas(ctx context.Context, in *BatchCreateRequestMetadatasRequest, opts ...grpc.CallOption) (*BatchCreateRequestMetadatasResponse, error) { + out := new(BatchCreateRequestMetadatasResponse) + err := c.cc.Invoke(ctx, IngesterService_BatchCreateRequestMetadatas_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *ingesterServiceClient) BatchCreateSpans(ctx context.Context, in *BatchCreateSpansRequest, opts ...grpc.CallOption) (*BatchCreateSpansResponse, error) { + out := new(BatchCreateSpansResponse) + err := c.cc.Invoke(ctx, IngesterService_BatchCreateSpans_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// IngesterServiceServer is the server API for IngesterService service. +// All implementations must embed UnimplementedIngesterServiceServer +// for forward compatibility +type IngesterServiceServer interface { + BatchCreateRequestMetadatas(context.Context, *BatchCreateRequestMetadatasRequest) (*BatchCreateRequestMetadatasResponse, error) + BatchCreateSpans(context.Context, *BatchCreateSpansRequest) (*BatchCreateSpansResponse, error) + mustEmbedUnimplementedIngesterServiceServer() +} + +// UnimplementedIngesterServiceServer must be embedded to have forward compatible implementations. +type UnimplementedIngesterServiceServer struct { +} + +func (UnimplementedIngesterServiceServer) BatchCreateRequestMetadatas(context.Context, *BatchCreateRequestMetadatasRequest) (*BatchCreateRequestMetadatasResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BatchCreateRequestMetadatas not implemented") +} +func (UnimplementedIngesterServiceServer) BatchCreateSpans(context.Context, *BatchCreateSpansRequest) (*BatchCreateSpansResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BatchCreateSpans not implemented") +} +func (UnimplementedIngesterServiceServer) mustEmbedUnimplementedIngesterServiceServer() {} + +// UnsafeIngesterServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to IngesterServiceServer will +// result in compilation errors. +type UnsafeIngesterServiceServer interface { + mustEmbedUnimplementedIngesterServiceServer() +} + +func RegisterIngesterServiceServer(s grpc.ServiceRegistrar, srv IngesterServiceServer) { + s.RegisterService(&IngesterService_ServiceDesc, srv) +} + +func _IngesterService_BatchCreateRequestMetadatas_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BatchCreateRequestMetadatasRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IngesterServiceServer).BatchCreateRequestMetadatas(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: IngesterService_BatchCreateRequestMetadatas_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IngesterServiceServer).BatchCreateRequestMetadatas(ctx, req.(*BatchCreateRequestMetadatasRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _IngesterService_BatchCreateSpans_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BatchCreateSpansRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IngesterServiceServer).BatchCreateSpans(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: IngesterService_BatchCreateSpans_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IngesterServiceServer).BatchCreateSpans(ctx, req.(*BatchCreateSpansRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// IngesterService_ServiceDesc is the grpc.ServiceDesc for IngesterService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var IngesterService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "k6.cloud.insights.proto.v1.ingester.IngesterService", + HandlerType: (*IngesterServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "BatchCreateRequestMetadatas", + Handler: _IngesterService_BatchCreateRequestMetadatas_Handler, + }, + { + MethodName: "BatchCreateSpans", + Handler: _IngesterService_BatchCreateSpans_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "v1/ingester/ingester.proto", +} diff --git a/cloudapi/insights/proto/v1/k6/labels.pb.go b/cloudapi/insights/proto/v1/k6/labels.pb.go new file mode 100644 index 00000000000..98bad511594 --- /dev/null +++ b/cloudapi/insights/proto/v1/k6/labels.pb.go @@ -0,0 +1,246 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v3.21.12 +// source: v1/k6/labels.proto + +package k6 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TestRunLabels struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` + Scenario string `protobuf:"bytes,2,opt,name=Scenario,proto3" json:"Scenario,omitempty"` + Group string `protobuf:"bytes,3,opt,name=Group,proto3" json:"Group,omitempty"` +} + +func (x *TestRunLabels) Reset() { + *x = TestRunLabels{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_k6_labels_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TestRunLabels) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestRunLabels) ProtoMessage() {} + +func (x *TestRunLabels) ProtoReflect() protoreflect.Message { + mi := &file_v1_k6_labels_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestRunLabels.ProtoReflect.Descriptor instead. +func (*TestRunLabels) Descriptor() ([]byte, []int) { + return file_v1_k6_labels_proto_rawDescGZIP(), []int{0} +} + +func (x *TestRunLabels) GetID() int64 { + if x != nil { + return x.ID + } + return 0 +} + +func (x *TestRunLabels) GetScenario() string { + if x != nil { + return x.Scenario + } + return "" +} + +func (x *TestRunLabels) GetGroup() string { + if x != nil { + return x.Group + } + return "" +} + +type HTTPLabels struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=Url,proto3" json:"Url,omitempty"` + Method string `protobuf:"bytes,2,opt,name=Method,proto3" json:"Method,omitempty"` + StatusCode int64 `protobuf:"varint,3,opt,name=StatusCode,proto3" json:"StatusCode,omitempty"` +} + +func (x *HTTPLabels) Reset() { + *x = HTTPLabels{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_k6_labels_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HTTPLabels) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HTTPLabels) ProtoMessage() {} + +func (x *HTTPLabels) ProtoReflect() protoreflect.Message { + mi := &file_v1_k6_labels_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HTTPLabels.ProtoReflect.Descriptor instead. +func (*HTTPLabels) Descriptor() ([]byte, []int) { + return file_v1_k6_labels_proto_rawDescGZIP(), []int{1} +} + +func (x *HTTPLabels) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *HTTPLabels) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *HTTPLabels) GetStatusCode() int64 { + if x != nil { + return x.StatusCode + } + return 0 +} + +var File_v1_k6_labels_proto protoreflect.FileDescriptor + +var file_v1_k6_labels_proto_rawDesc = []byte{ + 0x0a, 0x12, 0x76, 0x31, 0x2f, 0x6b, 0x36, 0x2f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1d, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, + 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, + 0x2e, 0x6b, 0x36, 0x22, 0x51, 0x0a, 0x0d, 0x54, 0x65, 0x73, 0x74, 0x52, 0x75, 0x6e, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x02, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x53, 0x63, 0x65, 0x6e, 0x61, 0x72, 0x69, 0x6f, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x53, 0x63, 0x65, 0x6e, 0x61, 0x72, 0x69, 0x6f, + 0x12, 0x14, 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x56, 0x0a, 0x0a, 0x48, 0x54, 0x54, 0x50, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x55, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1e, + 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x42, 0x2b, + 0x5a, 0x29, 0x67, 0x6f, 0x2e, 0x6b, 0x36, 0x2e, 0x69, 0x6f, 0x2f, 0x6b, 0x36, 0x2f, 0x63, 0x6c, + 0x6f, 0x75, 0x64, 0x61, 0x70, 0x69, 0x2f, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x2f, 0x6b, 0x36, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_v1_k6_labels_proto_rawDescOnce sync.Once + file_v1_k6_labels_proto_rawDescData = file_v1_k6_labels_proto_rawDesc +) + +func file_v1_k6_labels_proto_rawDescGZIP() []byte { + file_v1_k6_labels_proto_rawDescOnce.Do(func() { + file_v1_k6_labels_proto_rawDescData = protoimpl.X.CompressGZIP(file_v1_k6_labels_proto_rawDescData) + }) + return file_v1_k6_labels_proto_rawDescData +} + +var file_v1_k6_labels_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_v1_k6_labels_proto_goTypes = []interface{}{ + (*TestRunLabels)(nil), // 0: k6.cloud.insights.proto.v1.k6.TestRunLabels + (*HTTPLabels)(nil), // 1: k6.cloud.insights.proto.v1.k6.HTTPLabels +} +var file_v1_k6_labels_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_v1_k6_labels_proto_init() } +func file_v1_k6_labels_proto_init() { + if File_v1_k6_labels_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_v1_k6_labels_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestRunLabels); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_k6_labels_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HTTPLabels); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_v1_k6_labels_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_v1_k6_labels_proto_goTypes, + DependencyIndexes: file_v1_k6_labels_proto_depIdxs, + MessageInfos: file_v1_k6_labels_proto_msgTypes, + }.Build() + File_v1_k6_labels_proto = out.File + file_v1_k6_labels_proto_rawDesc = nil + file_v1_k6_labels_proto_goTypes = nil + file_v1_k6_labels_proto_depIdxs = nil +} diff --git a/cloudapi/insights/proto/v1/k6/labels.proto b/cloudapi/insights/proto/v1/k6/labels.proto new file mode 100644 index 00000000000..bda67ef82dc --- /dev/null +++ b/cloudapi/insights/proto/v1/k6/labels.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package k6.cloud.insights.proto.v1.k6; + +option go_package = "go.k6.io/k6/cloudapi/insights/proto/v1/k6"; + +message TestRunLabels { + int64 ID = 1; + string Scenario = 2; + string Group = 3; +} + +message HTTPLabels { + string Url = 1; + string Method = 2; + int64 StatusCode = 3; +} diff --git a/cloudapi/insights/proto/v1/k6/request_metadata.pb.go b/cloudapi/insights/proto/v1/k6/request_metadata.pb.go new file mode 100644 index 00000000000..f58dbc6a057 --- /dev/null +++ b/cloudapi/insights/proto/v1/k6/request_metadata.pb.go @@ -0,0 +1,224 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v3.21.12 +// source: v1/k6/request_metadata.proto + +package k6 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type RequestMetadata struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TraceID string `protobuf:"bytes,1,opt,name=TraceID,proto3" json:"TraceID,omitempty"` + StartTimeUnixNano int64 `protobuf:"varint,2,opt,name=StartTimeUnixNano,proto3" json:"StartTimeUnixNano,omitempty"` + EndTimeUnixNano int64 `protobuf:"varint,3,opt,name=EndTimeUnixNano,proto3" json:"EndTimeUnixNano,omitempty"` + TestRunLabels *TestRunLabels `protobuf:"bytes,4,opt,name=TestRunLabels,proto3" json:"TestRunLabels,omitempty"` + // Types that are assignable to ProtocolLabels: + // + // *RequestMetadata_HTTPLabels + ProtocolLabels isRequestMetadata_ProtocolLabels `protobuf_oneof:"ProtocolLabels"` +} + +func (x *RequestMetadata) Reset() { + *x = RequestMetadata{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_k6_request_metadata_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RequestMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestMetadata) ProtoMessage() {} + +func (x *RequestMetadata) ProtoReflect() protoreflect.Message { + mi := &file_v1_k6_request_metadata_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestMetadata.ProtoReflect.Descriptor instead. +func (*RequestMetadata) Descriptor() ([]byte, []int) { + return file_v1_k6_request_metadata_proto_rawDescGZIP(), []int{0} +} + +func (x *RequestMetadata) GetTraceID() string { + if x != nil { + return x.TraceID + } + return "" +} + +func (x *RequestMetadata) GetStartTimeUnixNano() int64 { + if x != nil { + return x.StartTimeUnixNano + } + return 0 +} + +func (x *RequestMetadata) GetEndTimeUnixNano() int64 { + if x != nil { + return x.EndTimeUnixNano + } + return 0 +} + +func (x *RequestMetadata) GetTestRunLabels() *TestRunLabels { + if x != nil { + return x.TestRunLabels + } + return nil +} + +func (m *RequestMetadata) GetProtocolLabels() isRequestMetadata_ProtocolLabels { + if m != nil { + return m.ProtocolLabels + } + return nil +} + +func (x *RequestMetadata) GetHTTPLabels() *HTTPLabels { + if x, ok := x.GetProtocolLabels().(*RequestMetadata_HTTPLabels); ok { + return x.HTTPLabels + } + return nil +} + +type isRequestMetadata_ProtocolLabels interface { + isRequestMetadata_ProtocolLabels() +} + +type RequestMetadata_HTTPLabels struct { + HTTPLabels *HTTPLabels `protobuf:"bytes,5,opt,name=HTTPLabels,proto3,oneof"` +} + +func (*RequestMetadata_HTTPLabels) isRequestMetadata_ProtocolLabels() {} + +var File_v1_k6_request_metadata_proto protoreflect.FileDescriptor + +var file_v1_k6_request_metadata_proto_rawDesc = []byte{ + 0x0a, 0x1c, 0x76, 0x31, 0x2f, 0x6b, 0x36, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1d, + 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x6b, 0x36, 0x1a, 0x12, 0x76, + 0x31, 0x2f, 0x6b, 0x36, 0x2f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0xb6, 0x02, 0x0a, 0x0f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 0x44, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 0x44, 0x12, + 0x2c, 0x0a, 0x11, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, + 0x4e, 0x61, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x28, 0x0a, + 0x0f, 0x45, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x45, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x55, + 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x52, 0x0a, 0x0d, 0x54, 0x65, 0x73, 0x74, 0x52, + 0x75, 0x6e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, + 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, + 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x6b, 0x36, 0x2e, 0x54, + 0x65, 0x73, 0x74, 0x52, 0x75, 0x6e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x0d, 0x54, 0x65, + 0x73, 0x74, 0x52, 0x75, 0x6e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x4b, 0x0a, 0x0a, 0x48, + 0x54, 0x54, 0x50, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x29, 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, + 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x6b, 0x36, 0x2e, + 0x48, 0x54, 0x54, 0x50, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x48, 0x00, 0x52, 0x0a, 0x48, 0x54, + 0x54, 0x50, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x42, 0x10, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x6f, + 0x2e, 0x6b, 0x36, 0x2e, 0x69, 0x6f, 0x2f, 0x6b, 0x36, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x61, + 0x70, 0x69, 0x2f, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x76, 0x31, 0x2f, 0x6b, 0x36, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_v1_k6_request_metadata_proto_rawDescOnce sync.Once + file_v1_k6_request_metadata_proto_rawDescData = file_v1_k6_request_metadata_proto_rawDesc +) + +func file_v1_k6_request_metadata_proto_rawDescGZIP() []byte { + file_v1_k6_request_metadata_proto_rawDescOnce.Do(func() { + file_v1_k6_request_metadata_proto_rawDescData = protoimpl.X.CompressGZIP(file_v1_k6_request_metadata_proto_rawDescData) + }) + return file_v1_k6_request_metadata_proto_rawDescData +} + +var file_v1_k6_request_metadata_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_v1_k6_request_metadata_proto_goTypes = []interface{}{ + (*RequestMetadata)(nil), // 0: k6.cloud.insights.proto.v1.k6.RequestMetadata + (*TestRunLabels)(nil), // 1: k6.cloud.insights.proto.v1.k6.TestRunLabels + (*HTTPLabels)(nil), // 2: k6.cloud.insights.proto.v1.k6.HTTPLabels +} +var file_v1_k6_request_metadata_proto_depIdxs = []int32{ + 1, // 0: k6.cloud.insights.proto.v1.k6.RequestMetadata.TestRunLabels:type_name -> k6.cloud.insights.proto.v1.k6.TestRunLabels + 2, // 1: k6.cloud.insights.proto.v1.k6.RequestMetadata.HTTPLabels:type_name -> k6.cloud.insights.proto.v1.k6.HTTPLabels + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_v1_k6_request_metadata_proto_init() } +func file_v1_k6_request_metadata_proto_init() { + if File_v1_k6_request_metadata_proto != nil { + return + } + file_v1_k6_labels_proto_init() + if !protoimpl.UnsafeEnabled { + file_v1_k6_request_metadata_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RequestMetadata); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_v1_k6_request_metadata_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*RequestMetadata_HTTPLabels)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_v1_k6_request_metadata_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_v1_k6_request_metadata_proto_goTypes, + DependencyIndexes: file_v1_k6_request_metadata_proto_depIdxs, + MessageInfos: file_v1_k6_request_metadata_proto_msgTypes, + }.Build() + File_v1_k6_request_metadata_proto = out.File + file_v1_k6_request_metadata_proto_rawDesc = nil + file_v1_k6_request_metadata_proto_goTypes = nil + file_v1_k6_request_metadata_proto_depIdxs = nil +} diff --git a/cloudapi/insights/proto/v1/k6/request_metadata.proto b/cloudapi/insights/proto/v1/k6/request_metadata.proto new file mode 100644 index 00000000000..cd32d1c83ae --- /dev/null +++ b/cloudapi/insights/proto/v1/k6/request_metadata.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package k6.cloud.insights.proto.v1.k6; + +option go_package = "go.k6.io/k6/cloudapi/insights/proto/v1/k6"; + +import "v1/k6/labels.proto"; + +message RequestMetadata { + string TraceID = 1; + int64 StartTimeUnixNano = 2; + int64 EndTimeUnixNano = 3; + TestRunLabels TestRunLabels = 4; + oneof ProtocolLabels { + HTTPLabels HTTPLabels = 5; + } +} diff --git a/cloudapi/insights/proto/v1/trace/labels.pb.go b/cloudapi/insights/proto/v1/trace/labels.pb.go new file mode 100644 index 00000000000..c414d450783 --- /dev/null +++ b/cloudapi/insights/proto/v1/trace/labels.pb.go @@ -0,0 +1,187 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v3.21.12 +// source: v1/trace/labels.proto + +package trace + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SpanLabels struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"` + ServiceName string `protobuf:"bytes,2,opt,name=ServiceName,proto3" json:"ServiceName,omitempty"` + Kind SpanKind `protobuf:"varint,3,opt,name=Kind,proto3,enum=k6.cloud.insights.proto.v1.trace.SpanKind" json:"Kind,omitempty"` + StatusCode SpanStatusCode `protobuf:"varint,4,opt,name=StatusCode,proto3,enum=k6.cloud.insights.proto.v1.trace.SpanStatusCode" json:"StatusCode,omitempty"` +} + +func (x *SpanLabels) Reset() { + *x = SpanLabels{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_trace_labels_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SpanLabels) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SpanLabels) ProtoMessage() {} + +func (x *SpanLabels) ProtoReflect() protoreflect.Message { + mi := &file_v1_trace_labels_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SpanLabels.ProtoReflect.Descriptor instead. +func (*SpanLabels) Descriptor() ([]byte, []int) { + return file_v1_trace_labels_proto_rawDescGZIP(), []int{0} +} + +func (x *SpanLabels) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SpanLabels) GetServiceName() string { + if x != nil { + return x.ServiceName + } + return "" +} + +func (x *SpanLabels) GetKind() SpanKind { + if x != nil { + return x.Kind + } + return SpanKind_SPAN_KIND_UNSPECIFIED +} + +func (x *SpanLabels) GetStatusCode() SpanStatusCode { + if x != nil { + return x.StatusCode + } + return SpanStatusCode_STATUS_CODE_UNSET +} + +var File_v1_trace_labels_proto protoreflect.FileDescriptor + +var file_v1_trace_labels_proto_rawDesc = []byte{ + 0x0a, 0x15, 0x76, 0x31, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x20, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x76, 0x31, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x1a, 0x13, 0x76, 0x31, 0x2f, 0x74, 0x72, + 0x61, 0x63, 0x65, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd4, + 0x01, 0x0a, 0x0a, 0x53, 0x70, 0x61, 0x6e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x12, 0x0a, + 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x2a, 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, + 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x74, + 0x72, 0x61, 0x63, 0x65, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x4b, + 0x69, 0x6e, 0x64, 0x12, 0x50, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x43, 0x6f, 0x64, 0x65, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x6f, 0x2e, 0x6b, 0x36, 0x2e, 0x69, + 0x6f, 0x2f, 0x6b, 0x36, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x61, 0x70, 0x69, 0x2f, 0x69, 0x6e, + 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x2f, + 0x74, 0x72, 0x61, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_v1_trace_labels_proto_rawDescOnce sync.Once + file_v1_trace_labels_proto_rawDescData = file_v1_trace_labels_proto_rawDesc +) + +func file_v1_trace_labels_proto_rawDescGZIP() []byte { + file_v1_trace_labels_proto_rawDescOnce.Do(func() { + file_v1_trace_labels_proto_rawDescData = protoimpl.X.CompressGZIP(file_v1_trace_labels_proto_rawDescData) + }) + return file_v1_trace_labels_proto_rawDescData +} + +var file_v1_trace_labels_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_v1_trace_labels_proto_goTypes = []interface{}{ + (*SpanLabels)(nil), // 0: k6.cloud.insights.proto.v1.trace.SpanLabels + (SpanKind)(0), // 1: k6.cloud.insights.proto.v1.trace.SpanKind + (SpanStatusCode)(0), // 2: k6.cloud.insights.proto.v1.trace.SpanStatusCode +} +var file_v1_trace_labels_proto_depIdxs = []int32{ + 1, // 0: k6.cloud.insights.proto.v1.trace.SpanLabels.Kind:type_name -> k6.cloud.insights.proto.v1.trace.SpanKind + 2, // 1: k6.cloud.insights.proto.v1.trace.SpanLabels.StatusCode:type_name -> k6.cloud.insights.proto.v1.trace.SpanStatusCode + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_v1_trace_labels_proto_init() } +func file_v1_trace_labels_proto_init() { + if File_v1_trace_labels_proto != nil { + return + } + file_v1_trace_span_proto_init() + if !protoimpl.UnsafeEnabled { + file_v1_trace_labels_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SpanLabels); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_v1_trace_labels_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_v1_trace_labels_proto_goTypes, + DependencyIndexes: file_v1_trace_labels_proto_depIdxs, + MessageInfos: file_v1_trace_labels_proto_msgTypes, + }.Build() + File_v1_trace_labels_proto = out.File + file_v1_trace_labels_proto_rawDesc = nil + file_v1_trace_labels_proto_goTypes = nil + file_v1_trace_labels_proto_depIdxs = nil +} diff --git a/cloudapi/insights/proto/v1/trace/labels.proto b/cloudapi/insights/proto/v1/trace/labels.proto new file mode 100644 index 00000000000..b5a479546d1 --- /dev/null +++ b/cloudapi/insights/proto/v1/trace/labels.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package k6.cloud.insights.proto.v1.trace; + +option go_package = "go.k6.io/k6/cloudapi/insights/proto/v1/trace"; + +import "v1/trace/span.proto"; + +message SpanLabels { + string Name = 1; + string ServiceName = 2; + SpanKind Kind = 3; + SpanStatusCode StatusCode = 4; +} diff --git a/cloudapi/insights/proto/v1/trace/span.pb.go b/cloudapi/insights/proto/v1/trace/span.pb.go new file mode 100644 index 00000000000..aad4db74bf1 --- /dev/null +++ b/cloudapi/insights/proto/v1/trace/span.pb.go @@ -0,0 +1,391 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v3.21.12 +// source: v1/trace/span.proto + +package trace + +import ( + common "go.k6.io/k6/cloudapi/insights/proto/v1/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// SpanKind is the type of span. +type SpanKind int32 + +const ( + // Unspecified. Do NOT use as default. + SpanKind_SPAN_KIND_UNSPECIFIED SpanKind = 0 + // Indicates that the span represents an internal operation within an application, + // as opposed to an operation happening at the boundaries. Default value. + SpanKind_SPAN_KIND_INTERNAL SpanKind = 1 + // Indicates that the span covers server-side handling of an RPC or other + // remote network request. + SpanKind_SPAN_KIND_SERVER SpanKind = 2 + // Indicates that the span describes a request to some remote service. + SpanKind_SPAN_KIND_CLIENT SpanKind = 3 + // Indicates that the span describes a producer sending a message to a broker. + SpanKind_SPAN_KIND_PRODUCER SpanKind = 4 + // Indicates that the span describes consumer receiving a message from a broker. + SpanKind_SPAN_KIND_CONSUMER SpanKind = 5 +) + +// Enum value maps for SpanKind. +var ( + SpanKind_name = map[int32]string{ + 0: "SPAN_KIND_UNSPECIFIED", + 1: "SPAN_KIND_INTERNAL", + 2: "SPAN_KIND_SERVER", + 3: "SPAN_KIND_CLIENT", + 4: "SPAN_KIND_PRODUCER", + 5: "SPAN_KIND_CONSUMER", + } + SpanKind_value = map[string]int32{ + "SPAN_KIND_UNSPECIFIED": 0, + "SPAN_KIND_INTERNAL": 1, + "SPAN_KIND_SERVER": 2, + "SPAN_KIND_CLIENT": 3, + "SPAN_KIND_PRODUCER": 4, + "SPAN_KIND_CONSUMER": 5, + } +) + +func (x SpanKind) Enum() *SpanKind { + p := new(SpanKind) + *p = x + return p +} + +func (x SpanKind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SpanKind) Descriptor() protoreflect.EnumDescriptor { + return file_v1_trace_span_proto_enumTypes[0].Descriptor() +} + +func (SpanKind) Type() protoreflect.EnumType { + return &file_v1_trace_span_proto_enumTypes[0] +} + +func (x SpanKind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SpanKind.Descriptor instead. +func (SpanKind) EnumDescriptor() ([]byte, []int) { + return file_v1_trace_span_proto_rawDescGZIP(), []int{0} +} + +// For the semantics of status codes see +// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status +type SpanStatusCode int32 + +const ( + // The default status. + SpanStatusCode_STATUS_CODE_UNSET SpanStatusCode = 0 + // The Span has been validated by an Application developers or Operator to have completed successfully. + SpanStatusCode_STATUS_CODE_OK SpanStatusCode = 1 + // The Span contains an error. + SpanStatusCode_STATUS_CODE_ERROR SpanStatusCode = 2 +) + +// Enum value maps for SpanStatusCode. +var ( + SpanStatusCode_name = map[int32]string{ + 0: "STATUS_CODE_UNSET", + 1: "STATUS_CODE_OK", + 2: "STATUS_CODE_ERROR", + } + SpanStatusCode_value = map[string]int32{ + "STATUS_CODE_UNSET": 0, + "STATUS_CODE_OK": 1, + "STATUS_CODE_ERROR": 2, + } +) + +func (x SpanStatusCode) Enum() *SpanStatusCode { + p := new(SpanStatusCode) + *p = x + return p +} + +func (x SpanStatusCode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SpanStatusCode) Descriptor() protoreflect.EnumDescriptor { + return file_v1_trace_span_proto_enumTypes[1].Descriptor() +} + +func (SpanStatusCode) Type() protoreflect.EnumType { + return &file_v1_trace_span_proto_enumTypes[1] +} + +func (x SpanStatusCode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SpanStatusCode.Descriptor instead. +func (SpanStatusCode) EnumDescriptor() ([]byte, []int) { + return file_v1_trace_span_proto_rawDescGZIP(), []int{1} +} + +type Span struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A unique identifier for a trace. + TraceID string `protobuf:"bytes,1,opt,name=TraceID,proto3" json:"TraceID,omitempty"` + // A unique identifier for a span within a trace. + SpanID string `protobuf:"bytes,2,opt,name=SpanID,proto3" json:"SpanID,omitempty"` + // The start time of the span. + StartTimeUnixNano int64 `protobuf:"fixed64,3,opt,name=StartTimeUnixNano,proto3" json:"StartTimeUnixNano,omitempty"` + // The end time of the span. + EndTimeUnixNano int64 `protobuf:"fixed64,4,opt,name=EndTimeUnixNano,proto3" json:"EndTimeUnixNano,omitempty"` + // A description of the span's operation. + Name string `protobuf:"bytes,5,opt,name=Name,proto3" json:"Name,omitempty"` + // Logical name of the service. + ServiceName string `protobuf:"bytes,6,opt,name=ServiceName,proto3" json:"ServiceName,omitempty"` + // Distinguishes between spans generated in a particular context. + Kind SpanKind `protobuf:"varint,7,opt,name=Kind,proto3,enum=k6.cloud.insights.proto.v1.trace.SpanKind" json:"Kind,omitempty"` + // The status code. + StatusCode SpanStatusCode `protobuf:"varint,8,opt,name=StatusCode,proto3,enum=k6.cloud.insights.proto.v1.trace.SpanStatusCode" json:"StatusCode,omitempty"` + // The attributes associated with the span. + // https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/resource/semantic_conventions + // https://github.com/opentracing/specification/blob/master/semantic_conventions.md#span-tags-table + Attributes []*common.KeyValue `protobuf:"bytes,9,rep,name=Attributes,proto3" json:"Attributes,omitempty"` +} + +func (x *Span) Reset() { + *x = Span{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_trace_span_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Span) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Span) ProtoMessage() {} + +func (x *Span) ProtoReflect() protoreflect.Message { + mi := &file_v1_trace_span_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Span.ProtoReflect.Descriptor instead. +func (*Span) Descriptor() ([]byte, []int) { + return file_v1_trace_span_proto_rawDescGZIP(), []int{0} +} + +func (x *Span) GetTraceID() string { + if x != nil { + return x.TraceID + } + return "" +} + +func (x *Span) GetSpanID() string { + if x != nil { + return x.SpanID + } + return "" +} + +func (x *Span) GetStartTimeUnixNano() int64 { + if x != nil { + return x.StartTimeUnixNano + } + return 0 +} + +func (x *Span) GetEndTimeUnixNano() int64 { + if x != nil { + return x.EndTimeUnixNano + } + return 0 +} + +func (x *Span) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Span) GetServiceName() string { + if x != nil { + return x.ServiceName + } + return "" +} + +func (x *Span) GetKind() SpanKind { + if x != nil { + return x.Kind + } + return SpanKind_SPAN_KIND_UNSPECIFIED +} + +func (x *Span) GetStatusCode() SpanStatusCode { + if x != nil { + return x.StatusCode + } + return SpanStatusCode_STATUS_CODE_UNSET +} + +func (x *Span) GetAttributes() []*common.KeyValue { + if x != nil { + return x.Attributes + } + return nil +} + +var File_v1_trace_span_proto protoreflect.FileDescriptor + +var file_v1_trace_span_proto_rawDesc = []byte{ + 0x0a, 0x13, 0x76, 0x31, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x20, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, + 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, + 0x31, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x1a, 0x16, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0xa5, 0x03, 0x0a, 0x04, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x54, 0x72, 0x61, 0x63, + 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x54, 0x72, 0x61, 0x63, 0x65, + 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x70, 0x61, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x53, 0x70, 0x61, 0x6e, 0x49, 0x44, 0x12, 0x2c, 0x0a, 0x11, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x10, 0x52, 0x11, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, + 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x28, 0x0a, 0x0f, 0x45, 0x6e, 0x64, 0x54, + 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x10, 0x52, 0x0f, 0x45, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, + 0x6e, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x76, 0x31, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x4b, 0x69, + 0x6e, 0x64, 0x52, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x50, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x6b, + 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, + 0x53, 0x70, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0a, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x4b, 0x0a, 0x0a, 0x41, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, + 0x2e, 0x6b, 0x36, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, + 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x41, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2a, 0x99, 0x01, 0x0a, 0x08, 0x53, 0x70, 0x61, 0x6e, + 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, + 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x16, 0x0a, 0x12, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x49, 0x4e, 0x54, + 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x50, 0x41, 0x4e, 0x5f, + 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x02, 0x12, 0x14, 0x0a, + 0x10, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, + 0x54, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, + 0x5f, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x45, 0x52, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x53, + 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x53, 0x55, 0x4d, 0x45, + 0x52, 0x10, 0x05, 0x2a, 0x52, 0x0a, 0x0e, 0x53, 0x70, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, + 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x45, 0x54, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, + 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x4f, 0x4b, 0x10, 0x01, + 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x6f, 0x2e, 0x6b, 0x36, + 0x2e, 0x69, 0x6f, 0x2f, 0x6b, 0x36, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x61, 0x70, 0x69, 0x2f, + 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, + 0x31, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_v1_trace_span_proto_rawDescOnce sync.Once + file_v1_trace_span_proto_rawDescData = file_v1_trace_span_proto_rawDesc +) + +func file_v1_trace_span_proto_rawDescGZIP() []byte { + file_v1_trace_span_proto_rawDescOnce.Do(func() { + file_v1_trace_span_proto_rawDescData = protoimpl.X.CompressGZIP(file_v1_trace_span_proto_rawDescData) + }) + return file_v1_trace_span_proto_rawDescData +} + +var file_v1_trace_span_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_v1_trace_span_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_v1_trace_span_proto_goTypes = []interface{}{ + (SpanKind)(0), // 0: k6.cloud.insights.proto.v1.trace.SpanKind + (SpanStatusCode)(0), // 1: k6.cloud.insights.proto.v1.trace.SpanStatusCode + (*Span)(nil), // 2: k6.cloud.insights.proto.v1.trace.Span + (*common.KeyValue)(nil), // 3: k6.cloud.insights.proto.v1.common.KeyValue +} +var file_v1_trace_span_proto_depIdxs = []int32{ + 0, // 0: k6.cloud.insights.proto.v1.trace.Span.Kind:type_name -> k6.cloud.insights.proto.v1.trace.SpanKind + 1, // 1: k6.cloud.insights.proto.v1.trace.Span.StatusCode:type_name -> k6.cloud.insights.proto.v1.trace.SpanStatusCode + 3, // 2: k6.cloud.insights.proto.v1.trace.Span.Attributes:type_name -> k6.cloud.insights.proto.v1.common.KeyValue + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_v1_trace_span_proto_init() } +func file_v1_trace_span_proto_init() { + if File_v1_trace_span_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_v1_trace_span_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Span); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_v1_trace_span_proto_rawDesc, + NumEnums: 2, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_v1_trace_span_proto_goTypes, + DependencyIndexes: file_v1_trace_span_proto_depIdxs, + EnumInfos: file_v1_trace_span_proto_enumTypes, + MessageInfos: file_v1_trace_span_proto_msgTypes, + }.Build() + File_v1_trace_span_proto = out.File + file_v1_trace_span_proto_rawDesc = nil + file_v1_trace_span_proto_goTypes = nil + file_v1_trace_span_proto_depIdxs = nil +} diff --git a/cloudapi/insights/proto/v1/trace/span.proto b/cloudapi/insights/proto/v1/trace/span.proto new file mode 100644 index 00000000000..97e9942f7da --- /dev/null +++ b/cloudapi/insights/proto/v1/trace/span.proto @@ -0,0 +1,72 @@ +syntax = "proto3"; + +package k6.cloud.insights.proto.v1.trace; + +option go_package = "go.k6.io/k6/cloudapi/insights/proto/v1/trace"; + +import "v1/common/common.proto"; + +message Span { + // A unique identifier for a trace. + string TraceID = 1; + + // A unique identifier for a span within a trace. + string SpanID = 2; + + // The start time of the span. + sfixed64 StartTimeUnixNano = 3; + + // The end time of the span. + sfixed64 EndTimeUnixNano = 4; + + // A description of the span's operation. + string Name = 5; + + // Logical name of the service. + string ServiceName = 6; + + // Distinguishes between spans generated in a particular context. + SpanKind Kind = 7; + + // The status code. + SpanStatusCode StatusCode = 8; + + // The attributes associated with the span. + // https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/resource/semantic_conventions + // https://github.com/opentracing/specification/blob/master/semantic_conventions.md#span-tags-table + repeated common.KeyValue Attributes = 9; +} + +// SpanKind is the type of span. +enum SpanKind { + // Unspecified. Do NOT use as default. + SPAN_KIND_UNSPECIFIED = 0; + + // Indicates that the span represents an internal operation within an application, + // as opposed to an operation happening at the boundaries. Default value. + SPAN_KIND_INTERNAL = 1; + + // Indicates that the span covers server-side handling of an RPC or other + // remote network request. + SPAN_KIND_SERVER = 2; + + // Indicates that the span describes a request to some remote service. + SPAN_KIND_CLIENT = 3; + + // Indicates that the span describes a producer sending a message to a broker. + SPAN_KIND_PRODUCER = 4; + + // Indicates that the span describes consumer receiving a message from a broker. + SPAN_KIND_CONSUMER = 5; +} + +// For the semantics of status codes see +// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status +enum SpanStatusCode { + // The default status. + STATUS_CODE_UNSET = 0; + // The Span has been validated by an Application developers or Operator to have completed successfully. + STATUS_CODE_OK = 1; + // The Span contains an error. + STATUS_CODE_ERROR = 2; +}; diff --git a/output/cloud/expv2/collect.go b/output/cloud/expv2/collect.go index c241ce1ce15..5e85e5b90f7 100644 --- a/output/cloud/expv2/collect.go +++ b/output/cloud/expv2/collect.go @@ -2,12 +2,24 @@ package expv2 import ( "errors" + "strconv" "sync" "time" + "go.k6.io/k6/cloudapi/insights" + "go.k6.io/k6/lib/netext/httpext" "go.k6.io/k6/metrics" ) +const ( + metadataTraceIDKey = "trace_id" + scenarioTag = "scenario" + groupTag = "group" + nameTag = "name" + methodTag = "method" + statusTag = "status" +) + type timeBucket struct { Time int64 Sinks map[metrics.TimeSeries]metricValue @@ -163,3 +175,94 @@ func (c *collector) timeFromBucketID(id int64) int64 { func (c *collector) bucketCutoffID() int64 { return c.nowFunc().Add(-c.waitPeriod).UnixNano() / int64(c.aggregationPeriod) } + +type rmCollector struct { + testRunID int64 + buffer insights.RequestMetadatas + bufferMu *sync.Mutex +} + +func newRequestMetadatasCollector(testRunID int64) *rmCollector { + return &rmCollector{ + testRunID: testRunID, + buffer: nil, + bufferMu: &sync.Mutex{}, + } +} + +func (c *rmCollector) CollectRequestMetadatas(sampleContainers []metrics.SampleContainer) { + if len(sampleContainers) < 1 { + return + } + + // TODO(lukasz, other-proto-support): Support grpc/websocket trails. + var newBuffer insights.RequestMetadatas + for _, sampleContainer := range sampleContainers { + trail, ok := sampleContainer.(*httpext.Trail) + if !ok { + continue + } + + traceID, found := trail.Metadata[metadataTraceIDKey] + if !found { + continue + } + + m := insights.RequestMetadata{ + TraceID: traceID, + Start: trail.EndTime.Add(-trail.Duration), + End: trail.EndTime, + TestRunLabels: insights.TestRunLabels{ + ID: c.testRunID, + Scenario: c.getStringTagFromTrail(trail, scenarioTag), + Group: c.getStringTagFromTrail(trail, groupTag), + }, + ProtocolLabels: insights.ProtocolHTTPLabels{ + URL: c.getStringTagFromTrail(trail, nameTag), + Method: c.getStringTagFromTrail(trail, methodTag), + StatusCode: c.getIntTagFromTrail(trail, statusTag), + }, + } + + newBuffer = append(newBuffer, m) + } + + if len(newBuffer) < 1 { + return + } + + c.bufferMu.Lock() + defer c.bufferMu.Unlock() + + c.buffer = append(c.buffer, newBuffer...) +} + +func (c *rmCollector) PopAll() insights.RequestMetadatas { + c.bufferMu.Lock() + defer c.bufferMu.Unlock() + + b := c.buffer + c.buffer = nil + return b +} + +func (c *rmCollector) getStringTagFromTrail(trail *httpext.Trail, key string) string { + if tag, found := trail.Tags.Get(key); found { + return tag + } + + return "unknown" +} + +func (c *rmCollector) getIntTagFromTrail(trail *httpext.Trail, key string) int64 { + if tag, found := trail.Tags.Get(key); found { + tagInt, err := strconv.ParseInt(tag, 10, 64) + if err != nil { + return 0 + } + + return tagInt + } + + return 0 +} diff --git a/output/cloud/expv2/collect_test.go b/output/cloud/expv2/collect_test.go index 20251a3c2ec..7f923ca93d2 100644 --- a/output/cloud/expv2/collect_test.go +++ b/output/cloud/expv2/collect_test.go @@ -1,11 +1,16 @@ package expv2 import ( + "sync" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "go.k6.io/k6/cloudapi/insights" + "go.k6.io/k6/lib/netext" + "go.k6.io/k6/lib/netext/httpext" "go.k6.io/k6/metrics" ) @@ -307,3 +312,137 @@ func TestBucketQPushPopConcurrency(t *testing.T) { } } } + +func Test_requestMetadatasCollector_CollectRequestMetadatas_DoesNothingWithEmptyData(t *testing.T) { + t.Parallel() + + // Given + testRunID := int64(1337) + col := newRequestMetadatasCollector(testRunID) + var data []metrics.SampleContainer + + // When + col.CollectRequestMetadatas(data) + + // Then + require.Empty(t, col.buffer) +} + +func Test_requestMetadatasCollector_CollectRequestMetadatas_FiltersAndStoresHTTPTrailsAsRequestMetadatas(t *testing.T) { + t.Parallel() + + // Given + testRunID := int64(1337) + col := newRequestMetadatasCollector(testRunID) + data := []metrics.SampleContainer{ + &httpext.Trail{ + EndTime: time.Unix(10, 0), + Duration: time.Second, + Tags: metrics.NewRegistry().RootTagSet(). + With(scenarioTag, "test-scenario-1"). + With(groupTag, "test-group-1"). + With(nameTag, "test-url-1"). + With(methodTag, "test-method-1"). + With(statusTag, "200"), + Metadata: map[string]string{ + metadataTraceIDKey: "test-trace-id-1", + }, + }, + &httpext.Trail{ + // HTTP trail without trace ID should be ignored + }, + &netext.NetTrail{ + // Net trail should be ignored + }, + &httpext.Trail{ + EndTime: time.Unix(20, 0), + Duration: time.Second, + Tags: metrics.NewRegistry().RootTagSet(). + With(scenarioTag, "test-scenario-2"). + With(groupTag, "test-group-2"). + With(nameTag, "test-url-2"). + With(methodTag, "test-method-2"). + With(statusTag, "401"), + Metadata: map[string]string{ + metadataTraceIDKey: "test-trace-id-2", + }, + }, + &httpext.Trail{ + EndTime: time.Unix(20, 0), + Duration: time.Second, + Tags: metrics.NewRegistry().RootTagSet(), + // HTTP trail without `trace_id` metadata key should be ignored + Metadata: map[string]string{}, + }, + &httpext.Trail{ + EndTime: time.Unix(20, 0), + Duration: time.Second, + // If no tags are present, output should be set to `unknown` + Tags: metrics.NewRegistry().RootTagSet(), + Metadata: map[string]string{ + metadataTraceIDKey: "test-trace-id-3", + }, + }, + } + + // When + col.CollectRequestMetadatas(data) + + // Then + require.Len(t, col.buffer, 3) + require.Contains(t, col.buffer, insights.RequestMetadata{ + TraceID: "test-trace-id-1", + Start: time.Unix(9, 0), + End: time.Unix(10, 0), + TestRunLabels: insights.TestRunLabels{ID: 1337, Scenario: "test-scenario-1", Group: "test-group-1"}, + ProtocolLabels: insights.ProtocolHTTPLabels{URL: "test-url-1", Method: "test-method-1", StatusCode: 200}, + }) + require.Contains(t, col.buffer, insights.RequestMetadata{ + TraceID: "test-trace-id-2", + Start: time.Unix(19, 0), + End: time.Unix(20, 0), + TestRunLabels: insights.TestRunLabels{ID: 1337, Scenario: "test-scenario-2", Group: "test-group-2"}, + ProtocolLabels: insights.ProtocolHTTPLabels{URL: "test-url-2", Method: "test-method-2", StatusCode: 401}, + }) + require.Contains(t, col.buffer, insights.RequestMetadata{ + TraceID: "test-trace-id-3", + Start: time.Unix(19, 0), + End: time.Unix(20, 0), + TestRunLabels: insights.TestRunLabels{ID: 1337, Scenario: "unknown", Group: "unknown"}, + ProtocolLabels: insights.ProtocolHTTPLabels{URL: "unknown", Method: "unknown", StatusCode: 0}, + }) +} + +func Test_requestMetadatasCollector_PopAll_DoesNothingWithEmptyData(t *testing.T) { + t.Parallel() + + // Given + data := insights.RequestMetadatas{ + { + TraceID: "test-trace-id-1", + Start: time.Unix(9, 0), + End: time.Unix(10, 0), + TestRunLabels: insights.TestRunLabels{ID: 1337, Scenario: "test-scenario-1", Group: "test-group-1"}, + ProtocolLabels: insights.ProtocolHTTPLabels{URL: "test-url-1", Method: "test-method-1", StatusCode: 200}, + }, + { + TraceID: "test-trace-id-2", + Start: time.Unix(19, 0), + End: time.Unix(20, 0), + TestRunLabels: insights.TestRunLabels{ID: 1337, Scenario: "unknown", Group: "unknown"}, + ProtocolLabels: insights.ProtocolHTTPLabels{URL: "unknown", Method: "unknown", StatusCode: 0}, + }, + } + col := &rmCollector{ + buffer: data, + bufferMu: &sync.Mutex{}, + } + + // When + got := col.PopAll() + + // Then + require.Nil(t, col.buffer) + require.Empty(t, col.buffer) + require.Equal(t, data, got) +} diff --git a/output/cloud/expv2/flush.go b/output/cloud/expv2/flush.go index 9aaf1560e35..bff946772d5 100644 --- a/output/cloud/expv2/flush.go +++ b/output/cloud/expv2/flush.go @@ -3,6 +3,7 @@ package expv2 import ( "context" + "go.k6.io/k6/cloudapi/insights" "go.k6.io/k6/metrics" "go.k6.io/k6/output/cloud/expv2/pbcloud" ) @@ -131,3 +132,30 @@ func (msb *metricSetBuilder) addTimeBucket(bucket timeBucket) { pbTimeSeries, timeSeries.Metric.Type, bucket.Time, sink) } } + +// insightsClient is an interface for sending request metadatas to the Insights API. +type insightsClient interface { + IngestRequestMetadatasBatch(context.Context, insights.RequestMetadatas) error + Close() error +} + +type requestMetadatasFlusher struct { + client insightsClient + collector requestMetadatasCollector +} + +func newTracesFlusher(client insightsClient, collector requestMetadatasCollector) *requestMetadatasFlusher { + return &requestMetadatasFlusher{ + client: client, + collector: collector, + } +} + +func (f *requestMetadatasFlusher) flush(ctx context.Context) error { + requestMetadatas := f.collector.PopAll() + if len(requestMetadatas) < 1 { + return nil + } + + return f.client.IngestRequestMetadatasBatch(ctx, requestMetadatas) +} diff --git a/output/cloud/expv2/flush_test.go b/output/cloud/expv2/flush_test.go index c64799bd769..9ac3fec5ac9 100644 --- a/output/cloud/expv2/flush_test.go +++ b/output/cloud/expv2/flush_test.go @@ -2,15 +2,87 @@ package expv2 import ( "context" + "errors" "strconv" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "go.k6.io/k6/cloudapi/insights" "go.k6.io/k6/metrics" "go.k6.io/k6/output/cloud/expv2/pbcloud" ) +type mockWorkingInsightsClient struct { + ingestRequestMetadatasBatchInvoked bool + dataSent bool + data insights.RequestMetadatas +} + +func (c *mockWorkingInsightsClient) IngestRequestMetadatasBatch(ctx context.Context, data insights.RequestMetadatas) error { + c.ingestRequestMetadatasBatchInvoked = true + + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + c.dataSent = true + c.data = data + + return nil +} + +func (c *mockWorkingInsightsClient) Close() error { + return nil +} + +type mockFailingInsightsClient struct { + err error +} + +func (c *mockFailingInsightsClient) IngestRequestMetadatasBatch(_ context.Context, _ insights.RequestMetadatas) error { + return c.err +} + +func (c *mockFailingInsightsClient) Close() error { + return nil +} + +type mockRequestMetadatasCollector struct { + data insights.RequestMetadatas +} + +func (m *mockRequestMetadatasCollector) CollectRequestMetadatas(_ []metrics.SampleContainer) { + panic("implement me") +} + +func (m *mockRequestMetadatasCollector) PopAll() insights.RequestMetadatas { + return m.data +} + +func newMockRequestMetadatas() insights.RequestMetadatas { + return insights.RequestMetadatas{ + { + TraceID: "test-trace-id-1", + Start: time.Unix(1337, 0), + End: time.Unix(1338, 0), + TestRunLabels: insights.TestRunLabels{ID: 1, Scenario: "test-scenario-1", Group: "test-group-1"}, + ProtocolLabels: insights.ProtocolHTTPLabels{URL: "test-url-1", Method: "test-method-1", StatusCode: 200}, + }, + { + TraceID: "test-trace-id-2", + Start: time.Unix(2337, 0), + End: time.Unix(2338, 0), + TestRunLabels: insights.TestRunLabels{ID: 1, Scenario: "test-scenario-2", Group: "test-group-2"}, + ProtocolLabels: insights.ProtocolHTTPLabels{URL: "test-url-2", Method: "test-method-2", StatusCode: 200}, + }, + } +} + // TODO: additional case // case: add when the metric already exist // case: add when the metric and the timeseries already exist @@ -96,3 +168,79 @@ func (pm *pusherMock) push(_ *pbcloud.MetricSet) error { pm.pushCalled++ return nil } + +func Test_tracesFlusher_Flush_ReturnsNoErrorWithWorkingInsightsClientAndNonCancelledContextAndNoData(t *testing.T) { + t.Parallel() + + // Given + data := insights.RequestMetadatas{} + cli := &mockWorkingInsightsClient{} + col := &mockRequestMetadatasCollector{data: data} + flusher := newTracesFlusher(cli, col) + + // When + err := flusher.flush(context.Background()) + + // Then + require.NoError(t, err) + require.False(t, cli.ingestRequestMetadatasBatchInvoked) + require.False(t, cli.dataSent) + require.Empty(t, cli.data) +} + +func Test_tracesFlusher_Flush_ReturnsNoErrorWithWorkingInsightsClientAndNonCancelledContextAndData(t *testing.T) { + t.Parallel() + + // Given + data := newMockRequestMetadatas() + cli := &mockWorkingInsightsClient{} + col := &mockRequestMetadatasCollector{data: data} + flusher := newTracesFlusher(cli, col) + + // When + err := flusher.flush(context.Background()) + + // Then + require.NoError(t, err) + require.True(t, cli.ingestRequestMetadatasBatchInvoked) + require.True(t, cli.dataSent) + require.Equal(t, data, cli.data) +} + +func Test_tracesFlusher_Flush_ReturnsErrorWithWorkingInsightsClientAndCancelledContext(t *testing.T) { + t.Parallel() + + // Given + data := newMockRequestMetadatas() + cli := &mockWorkingInsightsClient{} + col := &mockRequestMetadatasCollector{data: data} + flusher := newTracesFlusher(cli, col) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + // When + err := flusher.flush(ctx) + + // Then + require.Error(t, err) + require.True(t, cli.ingestRequestMetadatasBatchInvoked) + require.False(t, cli.dataSent) + require.Empty(t, cli.data) +} + +func Test_tracesFlusher_Flush_ReturnsErrorWithFailingInsightsClientAndNonCancelledContext(t *testing.T) { + t.Parallel() + + // Given + data := newMockRequestMetadatas() + testErr := errors.New("test-error") + cli := &mockFailingInsightsClient{err: testErr} + col := &mockRequestMetadatasCollector{data: data} + flusher := newTracesFlusher(cli, col) + + // When + err := flusher.flush(context.Background()) + + // Then + require.ErrorIs(t, err, testErr) +} diff --git a/output/cloud/expv2/output.go b/output/cloud/expv2/output.go index feff158e6b2..0e8e6e62dae 100644 --- a/output/cloud/expv2/output.go +++ b/output/cloud/expv2/output.go @@ -7,10 +7,12 @@ import ( "errors" "fmt" "net/http" + "strconv" "sync" "time" "go.k6.io/k6/cloudapi" + "go.k6.io/k6/cloudapi/insights" "go.k6.io/k6/errext" "go.k6.io/k6/errext/exitcodes" "go.k6.io/k6/metrics" @@ -19,6 +21,14 @@ import ( "github.com/sirupsen/logrus" ) +// requestMetadatasCollector is an interface for collecting request metadatas +// and retrieving them, so they can be flushed using a flusher. +type requestMetadatasCollector interface { + CollectRequestMetadatas([]metrics.SampleContainer) + PopAll() insights.RequestMetadatas +} + +// flusher is an interface for flushing data to the cloud. type flusher interface { flush(context.Context) error } @@ -35,6 +45,10 @@ type Output struct { collector *collector flushing flusher + insightsClient insightsClient + requestMetadatasCollector requestMetadatasCollector + requestMetadatasFlusher flusher + // wg tracks background goroutines wg sync.WaitGroup @@ -74,6 +88,7 @@ func (o *Output) SetTestRunStopCallback(stopFunc func(error)) { // for metric samples and send them to the cloud. func (o *Output) Start() error { o.logger.Debug("Starting...") + defer o.logger.Debug("Started!") var err error o.collector, err = newCollector( @@ -100,12 +115,45 @@ func (o *Output) Start() error { o.runFlushWorkers() o.periodicInvoke(o.config.AggregationPeriod.TimeDuration(), o.collectSamples) + if o.tracingEnabled() { + testRunID, err := strconv.ParseInt(o.referenceID, 10, 64) + if err != nil { + return err + } + o.requestMetadatasCollector = newRequestMetadatasCollector(testRunID) + + insightsClientConfig := insights.ClientConfig{ + IngesterHost: o.config.TracesHost.ValueOrZero(), + AuthConfig: insights.ClientAuthConfig{ + Enabled: true, + TestRunID: testRunID, + Token: o.config.Token.ValueOrZero(), + RequireTransportSecurity: true, + }, + TLSConfig: insights.ClientTLSConfig{ + Insecure: false, + }, + } + insightsClient := insights.NewClient(insightsClientConfig) + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + if err := insightsClient.Dial(ctx); err != nil { + return err + } + + o.insightsClient = insightsClient + o.requestMetadatasFlusher = newTracesFlusher(insightsClient, o.requestMetadatasCollector) + o.periodicInvoke(o.config.TracesPushInterval.TimeDuration(), o.flushRequestMetadatas) + } + o.logger.WithField("config", printableConfig(o.config)).Debug("Started!") + return nil } // StopWithTestError gracefully stops all metric emission from the output. -func (o *Output) StopWithTestError(testErr error) error { +func (o *Output) StopWithTestError(_ error) error { o.logger.Debug("Stopping...") defer o.logger.Debug("Stopped!") @@ -125,6 +173,14 @@ func (o *Output) StopWithTestError(testErr error) error { o.collectSamples() o.flushMetrics() + // Flush all the remaining request metadatas. + if o.tracingEnabled() { + o.flushRequestMetadatas() + if err := o.insightsClient.Close(); err != nil { + o.logger.WithError(err).Error("Failed to close the insights client") + } + } + return nil } @@ -201,8 +257,9 @@ func (o *Output) collectSamples() { samples := o.GetBufferedSamples() o.collector.CollectSamples(samples) - // TODO: other operations with the samples containers - // e.g. flushing the Metadata as tracing samples + if o.tracingEnabled() { + o.requestMetadatasCollector.CollectRequestMetadatas(samples) + } } // flushMetrics receives a set of metric samples. @@ -218,6 +275,21 @@ func (o *Output) flushMetrics() { o.logger.WithField("t", time.Since(start)).Debug("Successfully flushed buffered samples to the cloud") } +// flushRequestMetadatas periodically flushes traces collected in RequestMetadatasCollector using flusher. +func (o *Output) flushRequestMetadatas() { + start := time.Now() + + ctx, cancel := context.WithTimeout(context.Background(), o.config.TracesPushInterval.TimeDuration()) + defer cancel() + + err := o.requestMetadatasFlusher.flush(ctx) + if err != nil { + o.logger.WithError(err).WithField("t", time.Since(start)).Error("Failed to push trace samples to the cloud") + } + + o.logger.WithField("t", time.Since(start)).Debug("Successfully flushed buffered trace samples to the cloud") +} + // handleFlushError handles errors generated from the flushing operation. // It may interrupt the metric collection or invoke aborting of the test. // @@ -260,6 +332,18 @@ func (o *Output) handleFlushError(err error) { }) } +func (o *Output) tracingEnabled() bool { + // TODO(lukasz): Check if k6 x Tempo is enabled + // + // We want to check whether a given organization is + // eligible for k6 x Tempo feature. If it isn't, we may + // consider to skip the traces output. + // + // We currently don't have a backend API to check this + // information. + return o.config.TracesEnabled.ValueOrZero() +} + func printableConfig(c cloudapi.Config) map[string]any { m := map[string]any{ "host": c.Host.String, diff --git a/vendor/google.golang.org/grpc/test/bufconn/bufconn.go b/vendor/google.golang.org/grpc/test/bufconn/bufconn.go new file mode 100644 index 00000000000..3f77f4876eb --- /dev/null +++ b/vendor/google.golang.org/grpc/test/bufconn/bufconn.go @@ -0,0 +1,318 @@ +/* + * + * Copyright 2017 gRPC authors. + * + * 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. + * + */ + +// Package bufconn provides a net.Conn implemented by a buffer and related +// dialing and listening functionality. +package bufconn + +import ( + "context" + "fmt" + "io" + "net" + "sync" + "time" +) + +// Listener implements a net.Listener that creates local, buffered net.Conns +// via its Accept and Dial method. +type Listener struct { + mu sync.Mutex + sz int + ch chan net.Conn + done chan struct{} +} + +// Implementation of net.Error providing timeout +type netErrorTimeout struct { + error +} + +func (e netErrorTimeout) Timeout() bool { return true } +func (e netErrorTimeout) Temporary() bool { return false } + +var errClosed = fmt.Errorf("closed") +var errTimeout net.Error = netErrorTimeout{error: fmt.Errorf("i/o timeout")} + +// Listen returns a Listener that can only be contacted by its own Dialers and +// creates buffered connections between the two. +func Listen(sz int) *Listener { + return &Listener{sz: sz, ch: make(chan net.Conn), done: make(chan struct{})} +} + +// Accept blocks until Dial is called, then returns a net.Conn for the server +// half of the connection. +func (l *Listener) Accept() (net.Conn, error) { + select { + case <-l.done: + return nil, errClosed + case c := <-l.ch: + return c, nil + } +} + +// Close stops the listener. +func (l *Listener) Close() error { + l.mu.Lock() + defer l.mu.Unlock() + select { + case <-l.done: + // Already closed. + break + default: + close(l.done) + } + return nil +} + +// Addr reports the address of the listener. +func (l *Listener) Addr() net.Addr { return addr{} } + +// Dial creates an in-memory full-duplex network connection, unblocks Accept by +// providing it the server half of the connection, and returns the client half +// of the connection. +func (l *Listener) Dial() (net.Conn, error) { + return l.DialContext(context.Background()) +} + +// DialContext creates an in-memory full-duplex network connection, unblocks Accept by +// providing it the server half of the connection, and returns the client half +// of the connection. If ctx is Done, returns ctx.Err() +func (l *Listener) DialContext(ctx context.Context) (net.Conn, error) { + p1, p2 := newPipe(l.sz), newPipe(l.sz) + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-l.done: + return nil, errClosed + case l.ch <- &conn{p1, p2}: + return &conn{p2, p1}, nil + } +} + +type pipe struct { + mu sync.Mutex + + // buf contains the data in the pipe. It is a ring buffer of fixed capacity, + // with r and w pointing to the offset to read and write, respsectively. + // + // Data is read between [r, w) and written to [w, r), wrapping around the end + // of the slice if necessary. + // + // The buffer is empty if r == len(buf), otherwise if r == w, it is full. + // + // w and r are always in the range [0, cap(buf)) and [0, len(buf)]. + buf []byte + w, r int + + wwait sync.Cond + rwait sync.Cond + + // Indicate that a write/read timeout has occurred + wtimedout bool + rtimedout bool + + wtimer *time.Timer + rtimer *time.Timer + + closed bool + writeClosed bool +} + +func newPipe(sz int) *pipe { + p := &pipe{buf: make([]byte, 0, sz)} + p.wwait.L = &p.mu + p.rwait.L = &p.mu + + p.wtimer = time.AfterFunc(0, func() {}) + p.rtimer = time.AfterFunc(0, func() {}) + return p +} + +func (p *pipe) empty() bool { + return p.r == len(p.buf) +} + +func (p *pipe) full() bool { + return p.r < len(p.buf) && p.r == p.w +} + +func (p *pipe) Read(b []byte) (n int, err error) { + p.mu.Lock() + defer p.mu.Unlock() + // Block until p has data. + for { + if p.closed { + return 0, io.ErrClosedPipe + } + if !p.empty() { + break + } + if p.writeClosed { + return 0, io.EOF + } + if p.rtimedout { + return 0, errTimeout + } + + p.rwait.Wait() + } + wasFull := p.full() + + n = copy(b, p.buf[p.r:len(p.buf)]) + p.r += n + if p.r == cap(p.buf) { + p.r = 0 + p.buf = p.buf[:p.w] + } + + // Signal a blocked writer, if any + if wasFull { + p.wwait.Signal() + } + + return n, nil +} + +func (p *pipe) Write(b []byte) (n int, err error) { + p.mu.Lock() + defer p.mu.Unlock() + if p.closed { + return 0, io.ErrClosedPipe + } + for len(b) > 0 { + // Block until p is not full. + for { + if p.closed || p.writeClosed { + return 0, io.ErrClosedPipe + } + if !p.full() { + break + } + if p.wtimedout { + return 0, errTimeout + } + + p.wwait.Wait() + } + wasEmpty := p.empty() + + end := cap(p.buf) + if p.w < p.r { + end = p.r + } + x := copy(p.buf[p.w:end], b) + b = b[x:] + n += x + p.w += x + if p.w > len(p.buf) { + p.buf = p.buf[:p.w] + } + if p.w == cap(p.buf) { + p.w = 0 + } + + // Signal a blocked reader, if any. + if wasEmpty { + p.rwait.Signal() + } + } + return n, nil +} + +func (p *pipe) Close() error { + p.mu.Lock() + defer p.mu.Unlock() + p.closed = true + // Signal all blocked readers and writers to return an error. + p.rwait.Broadcast() + p.wwait.Broadcast() + return nil +} + +func (p *pipe) closeWrite() error { + p.mu.Lock() + defer p.mu.Unlock() + p.writeClosed = true + // Signal all blocked readers and writers to return an error. + p.rwait.Broadcast() + p.wwait.Broadcast() + return nil +} + +type conn struct { + io.Reader + io.Writer +} + +func (c *conn) Close() error { + err1 := c.Reader.(*pipe).Close() + err2 := c.Writer.(*pipe).closeWrite() + if err1 != nil { + return err1 + } + return err2 +} + +func (c *conn) SetDeadline(t time.Time) error { + c.SetReadDeadline(t) + c.SetWriteDeadline(t) + return nil +} + +func (c *conn) SetReadDeadline(t time.Time) error { + p := c.Reader.(*pipe) + p.mu.Lock() + defer p.mu.Unlock() + p.rtimer.Stop() + p.rtimedout = false + if !t.IsZero() { + p.rtimer = time.AfterFunc(time.Until(t), func() { + p.mu.Lock() + defer p.mu.Unlock() + p.rtimedout = true + p.rwait.Broadcast() + }) + } + return nil +} + +func (c *conn) SetWriteDeadline(t time.Time) error { + p := c.Writer.(*pipe) + p.mu.Lock() + defer p.mu.Unlock() + p.wtimer.Stop() + p.wtimedout = false + if !t.IsZero() { + p.wtimer = time.AfterFunc(time.Until(t), func() { + p.mu.Lock() + defer p.mu.Unlock() + p.wtimedout = true + p.wwait.Broadcast() + }) + } + return nil +} + +func (*conn) LocalAddr() net.Addr { return addr{} } +func (*conn) RemoteAddr() net.Addr { return addr{} } + +type addr struct{} + +func (addr) Network() string { return "bufconn" } +func (addr) String() string { return "bufconn" } diff --git a/vendor/modules.txt b/vendor/modules.txt index 7a78e3c08e9..e83617843be 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -402,6 +402,7 @@ google.golang.org/grpc/serviceconfig google.golang.org/grpc/stats google.golang.org/grpc/status google.golang.org/grpc/tap +google.golang.org/grpc/test/bufconn # google.golang.org/protobuf v1.30.0 ## explicit; go 1.11 google.golang.org/protobuf/encoding/protojson