diff --git a/backend/controller/console/console.go b/backend/controller/console/console.go index efba2b5fa5..ad53fdf5b4 100644 --- a/backend/controller/console/console.go +++ b/backend/controller/console/console.go @@ -8,39 +8,34 @@ import ( "time" "connectrpc.com/connect" - "github.com/alecthomas/types/optional" - "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" "github.com/TBD54566975/ftl/backend/controller/admin" "github.com/TBD54566975/ftl/backend/controller/dal" dalmodel "github.com/TBD54566975/ftl/backend/controller/dal/model" - "github.com/TBD54566975/ftl/backend/controller/timeline" pbconsole "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/console/v1" "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/console/v1/pbconsoleconnect" schemapb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/schema/v1" - pbtimeline "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1" + timelinepb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1" + "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1/timelinev1connect" ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" "github.com/TBD54566975/ftl/internal/buildengine" - "github.com/TBD54566975/ftl/internal/log" - "github.com/TBD54566975/ftl/internal/model" + "github.com/TBD54566975/ftl/internal/rpc" "github.com/TBD54566975/ftl/internal/schema" "github.com/TBD54566975/ftl/internal/slices" ) type ConsoleService struct { - dal *dal.DAL - timeline *timeline.Service - admin *admin.AdminService + dal *dal.DAL + admin *admin.AdminService } var _ pbconsoleconnect.ConsoleServiceHandler = (*ConsoleService)(nil) -func NewService(dal *dal.DAL, timeline *timeline.Service, admin *admin.AdminService) *ConsoleService { +func NewService(dal *dal.DAL, admin *admin.AdminService) *ConsoleService { return &ConsoleService{ - dal: dal, - timeline: timeline, - admin: admin, + dal: dal, + admin: admin, } } @@ -473,34 +468,37 @@ func addRefToSetMap(m map[schema.RefKey]map[schema.RefKey]bool, key schema.RefKe } func (c *ConsoleService) GetEvents(ctx context.Context, req *connect.Request[pbconsole.GetEventsRequest]) (*connect.Response[pbconsole.GetEventsResponse], error) { - query, err := eventsQueryProtoToDAL(req.Msg) + timelineReq, err := eventsQueryToTimelineQuery(req.Msg) if err != nil { return nil, err } if req.Msg.Limit == 0 { + // TODO: New timeline service does not yet treat 0 as an error return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("limit must be > 0")) } - limit := int(req.Msg.Limit) // Get 1 more than the requested limit to determine if there are more results. - limitPlusOne := limit + 1 + // TODO: Copy this logic into the timeline service. + limit := int(req.Msg.Limit) + timelineReq.Limit = req.Msg.Limit + 1 - results, err := c.timeline.QueryTimeline(ctx, limitPlusOne, query...) + client := rpc.ClientFromContext[timelinev1connect.TimelineServiceClient](ctx) + resp, err := client.GetTimeline(ctx, connect.NewRequest(timelineReq)) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get timeline: %w", err) } - var cursor *int64 // Return only the requested number of results. + results := resp.Msg.Events if len(results) > limit { - results = results[:limit] - id := results[len(results)-1].GetID() + results = resp.Msg.Events[:limit] + id := results[len(results)-1].Id cursor = &id } response := &pbconsole.GetEventsResponse{ - Events: slices.Map(results, eventDALToProto), + Events: results, Cursor: cursor, } return connect.NewResponse(response), nil @@ -517,29 +515,38 @@ func (c *ConsoleService) StreamEvents(ctx context.Context, req *connect.Request[ return connect.NewError(connect.CodeInvalidArgument, errors.New("limit must be > 0")) } - query, err := eventsQueryProtoToDAL(req.Msg.Query) + timelineReq, err := eventsQueryToTimelineQuery(req.Msg.Query) if err != nil { return err } + client := rpc.ClientFromContext[timelinev1connect.TimelineServiceClient](ctx) + // Default to last 1 day of events var lastEventTime time.Time for { thisRequestTime := time.Now() - newQuery := query + newQuery := timelineReq if !lastEventTime.IsZero() { - newQuery = append(newQuery, timeline.FilterTimeRange(thisRequestTime, lastEventTime)) + newQuery.Filters = append(newQuery.Filters, &timelinepb.GetTimelineRequest_Filter{ + Filter: &timelinepb.GetTimelineRequest_Filter_Time{ + Time: &timelinepb.GetTimelineRequest_TimeFilter{ + NewerThan: timestamppb.New(lastEventTime), + OlderThan: timestamppb.New(thisRequestTime), + }, + }, + }) } - events, err := c.timeline.QueryTimeline(ctx, int(req.Msg.Query.Limit), newQuery...) + resp, err := client.GetTimeline(ctx, connect.NewRequest(newQuery)) if err != nil { - return err + return fmt.Errorf("failed to get timeline: %w", err) } - if len(events) > 0 { + if len(resp.Msg.Events) > 0 { err = stream.Send(&pbconsole.StreamEventsResponse{ - Events: slices.Map(events, eventDALToProto), + Events: resp.Msg.Events, }) if err != nil { return err @@ -556,306 +563,91 @@ func (c *ConsoleService) StreamEvents(ctx context.Context, req *connect.Request[ } //nolint:maintidx -func eventsQueryProtoToDAL(query *pbconsole.GetEventsRequest) ([]timeline.TimelineFilter, error) { - var result []timeline.TimelineFilter +func eventsQueryToTimelineQuery(query *pbconsole.GetEventsRequest) (*timelinepb.GetTimelineRequest, error) { + req := &timelinepb.GetTimelineRequest{ + Filters: []*timelinepb.GetTimelineRequest_Filter{}, + } if query.Order == pbconsole.GetEventsRequest_ORDER_DESC { - result = append(result, timeline.FilterDescending()) + req.Order = timelinepb.GetTimelineRequest_ORDER_DESC } for _, filter := range query.Filters { switch f := filter.Filter.(type) { case *pbconsole.GetEventsRequest_Filter_Deployments: - deploymentKeys := make([]model.DeploymentKey, 0, len(f.Deployments.Deployments)) - for _, deployment := range f.Deployments.Deployments { - deploymentKey, err := model.ParseDeploymentKey(deployment) - if err != nil { - return nil, connect.NewError(connect.CodeInvalidArgument, err) - } - deploymentKeys = append(deploymentKeys, deploymentKey) - } - result = append(result, timeline.FilterDeployments(deploymentKeys...)) - - case *pbconsole.GetEventsRequest_Filter_Requests: - requestKeys := make([]model.RequestKey, 0, len(f.Requests.Requests)) - for _, request := range f.Requests.Requests { - requestKey, err := model.ParseRequestKey(request) - if err != nil { - return nil, connect.NewError(connect.CodeInvalidArgument, err) - } - requestKeys = append(requestKeys, requestKey) - } - result = append(result, timeline.FilterRequests(requestKeys...)) - - case *pbconsole.GetEventsRequest_Filter_EventTypes: - var types []timeline.EventType - for _, t := range f.EventTypes.EventTypes { - types = append(types, timeline.EventType(t)) - } - result = append(result, timeline.FilterTypes(types...)) - - case *pbconsole.GetEventsRequest_Filter_LogLevel: - level := log.Level(f.LogLevel.LogLevel) - if level < log.Trace || level > log.Error { - return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("unknown log level %v", f.LogLevel.LogLevel)) - } - result = append(result, timeline.FilterLogLevel(level)) - - case *pbconsole.GetEventsRequest_Filter_Time: - newerThan := f.Time.GetNewerThan().AsTime() - olderThan := f.Time.GetOlderThan().AsTime() - result = append(result, timeline.FilterTimeRange(olderThan, newerThan)) - - case *pbconsole.GetEventsRequest_Filter_Id: - lowerThan := f.Id.GetLowerThan() - higherThan := f.Id.GetHigherThan() - result = append(result, timeline.FilterIDRange(lowerThan, higherThan)) - - case *pbconsole.GetEventsRequest_Filter_Call: - sourceModule := optional.Zero(f.Call.GetSourceModule()) - destVerb := optional.Zero(f.Call.GetDestVerb()) - result = append(result, timeline.FilterCall(sourceModule, f.Call.DestModule, destVerb)) - - default: - return nil, fmt.Errorf("unknown filter type %T", f) - } - } - - return result, nil -} - -//nolint:maintidx -func eventDALToProto(event timeline.Event) *pbtimeline.Event { - switch event := event.(type) { - case *timeline.CallEvent: - var requestKey *string - if r, ok := event.RequestKey.Get(); ok { - rstr := r.String() - requestKey = &rstr - } - var sourceVerbRef *schemapb.Ref - if sourceVerb, ok := event.SourceVerb.Get(); ok { - sourceVerbRef = sourceVerb.ToProto().(*schemapb.Ref) //nolint:forcetypeassert - } - return &pbtimeline.Event{ - TimeStamp: timestamppb.New(event.Time), - Id: event.ID, - Entry: &pbtimeline.Event_Call{ - Call: &pbtimeline.CallEvent{ - RequestKey: requestKey, - DeploymentKey: event.DeploymentKey.String(), - TimeStamp: timestamppb.New(event.Time), - SourceVerbRef: sourceVerbRef, - DestinationVerbRef: &schemapb.Ref{ - Module: event.DestVerb.Module, - Name: event.DestVerb.Name, + req.Filters = append(req.Filters, &timelinepb.GetTimelineRequest_Filter{ + Filter: &timelinepb.GetTimelineRequest_Filter_Deployments{ + Deployments: &timelinepb.GetTimelineRequest_DeploymentFilter{ + Deployments: f.Deployments.Deployments, }, - Duration: durationpb.New(event.Duration), - Request: string(event.Request), - Response: string(event.Response), - Error: event.Error.Ptr(), - Stack: event.Stack.Ptr(), }, - }, - } + }) - case *timeline.LogEvent: - var requestKey *string - if r, ok := event.RequestKey.Get(); ok { - rstr := r.String() - requestKey = &rstr - } - return &pbtimeline.Event{ - TimeStamp: timestamppb.New(event.Time), - Id: event.ID, - Entry: &pbtimeline.Event_Log{ - Log: &pbtimeline.LogEvent{ - DeploymentKey: event.DeploymentKey.String(), - RequestKey: requestKey, - TimeStamp: timestamppb.New(event.Time), - LogLevel: event.Level, - Attributes: event.Attributes, - Message: event.Message, - Error: event.Error.Ptr(), + case *pbconsole.GetEventsRequest_Filter_Requests: + req.Filters = append(req.Filters, &timelinepb.GetTimelineRequest_Filter{ + Filter: &timelinepb.GetTimelineRequest_Filter_Requests{ + Requests: &timelinepb.GetTimelineRequest_RequestFilter{ + Requests: f.Requests.Requests, + }, }, - }, - } + }) - case *timeline.DeploymentCreatedEvent: - var replaced *string - if r, ok := event.ReplacedDeployment.Get(); ok { - rstr := r.String() - replaced = &rstr - } - return &pbtimeline.Event{ - TimeStamp: timestamppb.New(event.Time), - Id: event.ID, - Entry: &pbtimeline.Event_DeploymentCreated{ - DeploymentCreated: &pbtimeline.DeploymentCreatedEvent{ - Key: event.DeploymentKey.String(), - Language: event.Language, - ModuleName: event.ModuleName, - MinReplicas: int32(event.MinReplicas), - Replaced: replaced, - }, - }, - } - case *timeline.DeploymentUpdatedEvent: - return &pbtimeline.Event{ - TimeStamp: timestamppb.New(event.Time), - Id: event.ID, - Entry: &pbtimeline.Event_DeploymentUpdated{ - DeploymentUpdated: &pbtimeline.DeploymentUpdatedEvent{ - Key: event.DeploymentKey.String(), - MinReplicas: int32(event.MinReplicas), - PrevMinReplicas: int32(event.PrevMinReplicas), + case *pbconsole.GetEventsRequest_Filter_EventTypes: + req.Filters = append(req.Filters, &timelinepb.GetTimelineRequest_Filter{ + Filter: &timelinepb.GetTimelineRequest_Filter_EventTypes{ + EventTypes: &timelinepb.GetTimelineRequest_EventTypeFilter{ + EventTypes: f.EventTypes.EventTypes, + }, }, - }, - } - - case *timeline.IngressEvent: - var requestKey *string - if r, ok := event.RequestKey.Get(); ok { - rstr := r.String() - requestKey = &rstr - } + }) - return &pbtimeline.Event{ - TimeStamp: timestamppb.New(event.Time), - Id: event.ID, - Entry: &pbtimeline.Event_Ingress{ - Ingress: &pbtimeline.IngressEvent{ - DeploymentKey: event.DeploymentKey.String(), - RequestKey: requestKey, - VerbRef: &schemapb.Ref{ - Module: event.Verb.Module, - Name: event.Verb.Name, + case *pbconsole.GetEventsRequest_Filter_LogLevel: + req.Filters = append(req.Filters, &timelinepb.GetTimelineRequest_Filter{ + Filter: &timelinepb.GetTimelineRequest_Filter_LogLevel{ + LogLevel: &timelinepb.GetTimelineRequest_LogLevelFilter{ + LogLevel: f.LogLevel.LogLevel, }, - Method: event.Method, - Path: event.Path, - StatusCode: int32(event.StatusCode), - TimeStamp: timestamppb.New(event.Time), - Duration: durationpb.New(event.Duration), - Request: string(event.Request), - RequestHeader: string(event.RequestHeader), - Response: string(event.Response), - ResponseHeader: string(event.ResponseHeader), - Error: event.Error.Ptr(), }, - }, - } + }) - case *timeline.CronScheduledEvent: - return &pbtimeline.Event{ - TimeStamp: timestamppb.New(event.Time), - Id: event.ID, - Entry: &pbtimeline.Event_CronScheduled{ - CronScheduled: &pbtimeline.CronScheduledEvent{ - DeploymentKey: event.DeploymentKey.String(), - VerbRef: &schemapb.Ref{ - Module: event.Verb.Module, - Name: event.Verb.Name, + case *pbconsole.GetEventsRequest_Filter_Time: + req.Filters = append(req.Filters, &timelinepb.GetTimelineRequest_Filter{ + Filter: &timelinepb.GetTimelineRequest_Filter_Time{ + Time: &timelinepb.GetTimelineRequest_TimeFilter{ + OlderThan: f.Time.OlderThan, + NewerThan: f.Time.NewerThan, }, - TimeStamp: timestamppb.New(event.Time), - Duration: durationpb.New(event.Duration), - ScheduledAt: timestamppb.New(event.ScheduledAt), - Schedule: event.Schedule, - Error: event.Error.Ptr(), }, - }, - } - - case *timeline.AsyncExecuteEvent: - var requestKey *string - if rstr, ok := event.RequestKey.Get(); ok { - requestKey = &rstr - } - - var asyncEventType pbtimeline.AsyncExecuteEventType - switch event.EventType { - case timeline.AsyncExecuteEventTypeUnkown: - asyncEventType = pbtimeline.AsyncExecuteEventType_ASYNC_EXECUTE_EVENT_TYPE_UNSPECIFIED - case timeline.AsyncExecuteEventTypeCron: - asyncEventType = pbtimeline.AsyncExecuteEventType_ASYNC_EXECUTE_EVENT_TYPE_CRON - case timeline.AsyncExecuteEventTypePubSub: - asyncEventType = pbtimeline.AsyncExecuteEventType_ASYNC_EXECUTE_EVENT_TYPE_PUBSUB - } + }) - return &pbtimeline.Event{ - TimeStamp: timestamppb.New(event.Time), - Id: event.ID, - Entry: &pbtimeline.Event_AsyncExecute{ - AsyncExecute: &pbtimeline.AsyncExecuteEvent{ - DeploymentKey: event.DeploymentKey.String(), - RequestKey: requestKey, - TimeStamp: timestamppb.New(event.Time), - AsyncEventType: asyncEventType, - VerbRef: &schemapb.Ref{ - Module: event.Verb.Module, - Name: event.Verb.Name, + case *pbconsole.GetEventsRequest_Filter_Id: + req.Filters = append(req.Filters, &timelinepb.GetTimelineRequest_Filter{ + Filter: &timelinepb.GetTimelineRequest_Filter_Id{ + Id: &timelinepb.GetTimelineRequest_IDFilter{ + HigherThan: f.Id.HigherThan, + LowerThan: f.Id.LowerThan, }, - Duration: durationpb.New(event.Duration), - Error: event.Error.Ptr(), }, - }, - } - - case *timeline.PubSubPublishEvent: - var requestKey *string - if r, ok := event.RequestKey.Get(); ok { - requestKey = &r - } + }) - return &pbtimeline.Event{ - TimeStamp: timestamppb.New(event.Time), - Id: event.ID, - Entry: &pbtimeline.Event_PubsubPublish{ - PubsubPublish: &pbtimeline.PubSubPublishEvent{ - DeploymentKey: event.DeploymentKey.String(), - RequestKey: requestKey, - VerbRef: event.SourceVerb.ToProto().(*schemapb.Ref), //nolint:forcetypeassert - TimeStamp: timestamppb.New(event.Time), - Duration: durationpb.New(event.Duration), - Topic: event.Topic, - Request: string(event.Request), - Error: event.Error.Ptr(), + case *pbconsole.GetEventsRequest_Filter_Call: + req.Filters = append(req.Filters, &timelinepb.GetTimelineRequest_Filter{ + Filter: &timelinepb.GetTimelineRequest_Filter_Call{ + Call: &timelinepb.GetTimelineRequest_CallFilter{ + DestModule: f.Call.DestModule, + DestVerb: f.Call.DestVerb, + SourceModule: f.Call.SourceModule, + }, }, - }, - } - - case *timeline.PubSubConsumeEvent: - var requestKey *string - if r, ok := event.RequestKey.Get(); ok { - requestKey = &r - } - - var destVerbModule string - var destVerbName string - if destVerb, ok := event.DestVerb.Get(); ok { - destVerbModule = destVerb.Module - destVerbName = destVerb.Name - } + }) + case *pbconsole.GetEventsRequest_Filter_Limit: + req.Limit = f.Limit.Limit - return &pbtimeline.Event{ - TimeStamp: timestamppb.New(event.Time), - Id: event.ID, - Entry: &pbtimeline.Event_PubsubConsume{ - PubsubConsume: &pbtimeline.PubSubConsumeEvent{ - DeploymentKey: event.DeploymentKey.String(), - RequestKey: requestKey, - DestVerbModule: &destVerbModule, - DestVerbName: &destVerbName, - TimeStamp: timestamppb.New(event.Time), - Duration: durationpb.New(event.Duration), - Topic: event.Topic, - Error: event.Error.Ptr(), - }, - }, + default: + return nil, fmt.Errorf("unknown filter type %T", f) } - - default: - panic(fmt.Errorf("unknown event type %T", event)) } + return req, nil } func (c *ConsoleService) GetConfig(ctx context.Context, req *connect.Request[pbconsole.GetConfigRequest]) (*connect.Response[pbconsole.GetConfigResponse], error) { diff --git a/backend/controller/controller.go b/backend/controller/controller.go index d931fd6368..ebf5e4ad88 100644 --- a/backend/controller/controller.go +++ b/backend/controller/controller.go @@ -23,6 +23,7 @@ import ( "github.com/alecthomas/kong" "github.com/alecthomas/types/either" "github.com/alecthomas/types/optional" + "github.com/alecthomas/types/result" "github.com/jackc/pgx/v5" "github.com/jellydator/ttlcache/v3" "github.com/jpillora/backoff" @@ -44,7 +45,6 @@ import ( "github.com/TBD54566975/ftl/backend/controller/observability" "github.com/TBD54566975/ftl/backend/controller/pubsub" "github.com/TBD54566975/ftl/backend/controller/scheduledtask" - "github.com/TBD54566975/ftl/backend/controller/timeline" "github.com/TBD54566975/ftl/backend/libdal" "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/console/v1/pbconsoleconnect" ftldeployment "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/deployment/v1" @@ -52,8 +52,11 @@ import ( ftllease "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/lease/v1" leaseconnect "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/lease/v1/ftlv1connect" schemapb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/schema/v1" + timelinepb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1" + "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1/timelinev1connect" ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect" + "github.com/TBD54566975/ftl/backend/timeline" frontend "github.com/TBD54566975/ftl/frontend/console" "github.com/TBD54566975/ftl/internal/configuration" cf "github.com/TBD54566975/ftl/internal/configuration/manager" @@ -155,7 +158,7 @@ func Start( logger.Debugf("Advertising as %s", config.Advertise) admin := admin.NewAdminService(cm, sm, svc.dal) - console := console.NewService(svc.dal, svc.timeline, admin) + console := console.NewService(svc.dal, admin) g, ctx := errgroup.WithContext(ctx) @@ -205,7 +208,6 @@ type Service struct { tasks *scheduledtask.Scheduler pubSub *pubsub.Service registry artefacts.Service - timeline *timeline.Service controllerListListeners []ControllerListListener // Map from runnerKey.String() to client. @@ -270,13 +272,11 @@ func New( } svc.registry = storage - timelineSvc := timeline.New(ctx, conn, encryption) - svc.timeline = timelineSvc - pubSub := pubsub.New(ctx, conn, encryption, optional.Some[pubsub.AsyncCallListener](svc), timelineSvc) + pubSub := pubsub.New(ctx, conn, encryption, optional.Some[pubsub.AsyncCallListener](svc)) svc.pubSub = pubSub svc.dal = dal.New(ctx, conn, encryption, pubSub, svc.registry) - svc.deploymentLogsSink = newDeploymentLogsSink(ctx, timelineSvc) + svc.deploymentLogsSink = newDeploymentLogsSink(ctx) // Use min, max backoff if we are running in production, otherwise use // (1s, 1s) (or develBackoff). Will also wrap the job such that it its next @@ -458,7 +458,7 @@ func (s *Service) StreamDeploymentLogs(ctx context.Context, stream *connect.Clie requestKey = optional.Some(rkey) } - s.timeline.EnqueueEvent(ctx, &timeline.Log{ + timeline.Publish(ctx, timeline.Log{ DeploymentKey: deploymentKey, RequestKey: requestKey, Time: msg.TimeStamp.AsTime(), @@ -844,7 +844,7 @@ func (s *Service) PublishEvent(ctx context.Context, req *connect.Request[ftldepl module := req.Msg.Topic.Module route, ok := sstate.routes[module] if ok { - s.timeline.EnqueueEvent(ctx, &timeline.PubSubPublish{ + timeline.Publish(ctx, timeline.PubSubPublish{ DeploymentKey: route.Deployment, RequestKey: requestKey, Time: now, @@ -956,16 +956,16 @@ func (s *Service) callWithRequest( if currentCaller != nil && currentCaller.Module != module && !verb.IsExported() { observability.Calls.Request(ctx, req.Msg.Verb, start, optional.Some("invalid request: verb not exported")) err = connect.NewError(connect.CodePermissionDenied, fmt.Errorf("verb %q is not exported", verbRef)) - callEvent.Response = either.RightOf[*ftlv1.CallResponse](err) - s.timeline.EnqueueEvent(ctx, callEvent) + callEvent.Response = result.Err[*ftlv1.CallResponse](err) + timeline.Publish(ctx, callEvent) return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("verb %q is not exported", verbRef)) } err = validateCallBody(req.Msg.Body, verb, sch) if err != nil { observability.Calls.Request(ctx, req.Msg.Verb, start, optional.Some("invalid request: invalid call body")) - callEvent.Response = either.RightOf[*ftlv1.CallResponse](err) - s.timeline.EnqueueEvent(ctx, callEvent) + callEvent.Response = result.Err[*ftlv1.CallResponse](err) + timeline.Publish(ctx, callEvent) return nil, err } @@ -982,15 +982,15 @@ func (s *Service) callWithRequest( var resp *connect.Response[ftlv1.CallResponse] if err == nil { resp = connect.NewResponse(response.Msg) - callEvent.Response = either.LeftOf[error](resp.Msg) + callEvent.Response = result.Ok(resp.Msg) observability.Calls.Request(ctx, req.Msg.Verb, start, optional.None[string]()) } else { - callEvent.Response = either.RightOf[*ftlv1.CallResponse](err) + callEvent.Response = result.Err[*ftlv1.CallResponse](err) observability.Calls.Request(ctx, req.Msg.Verb, start, optional.Some("verb call failed")) logger.Errorf(err, "Call failed to verb %s for deployment %s", verbRef.String(), route.Deployment) } - s.timeline.EnqueueEvent(ctx, callEvent) + timeline.Publish(ctx, callEvent) return resp, err } @@ -1197,7 +1197,7 @@ func (s *Service) executeAsyncCalls(ctx context.Context) (interval time.Duration if e, ok := err.Get(); ok { errStr = optional.Some(e.Error()) } - s.timeline.EnqueueEvent(ctx, &timeline.AsyncExecute{ + timeline.Publish(ctx, timeline.AsyncExecute{ DeploymentKey: route.Deployment, RequestKey: call.ParentRequestKey, EventType: eventType, @@ -1718,12 +1718,17 @@ func (s *Service) reapCallEvents(ctx context.Context) (time.Duration, error) { return time.Hour, nil } - removed, err := s.timeline.DeleteOldEvents(ctx, timeline.EventTypeCall, *s.config.EventLogRetention) + // TODO: move this to timeline service completely? + client := rpc.ClientFromContext[timelinev1connect.TimelineServiceClient](ctx) + resp, err := client.DeleteOldEvents(ctx, connect.NewRequest(&timelinepb.DeleteOldEventsRequest{ + EventType: timelinepb.EventType_EVENT_TYPE_CALL, + AgeSeconds: int64(s.config.EventLogRetention.Seconds()), + })) if err != nil { return 0, fmt.Errorf("failed to prune call events: %w", err) } - if removed > 0 { - logger.Debugf("Pruned %d call events older than %s", removed, s.config.EventLogRetention) + if resp.Msg.DeletedCount > 0 { + logger.Debugf("Pruned %d call events older than %s", resp.Msg.DeletedCount, s.config.EventLogRetention) } // Prune every 5% of the retention period. diff --git a/backend/controller/dal/async_calls_test.go b/backend/controller/dal/async_calls_test.go index 457bc7ec5e..5e678d9ef3 100644 --- a/backend/controller/dal/async_calls_test.go +++ b/backend/controller/dal/async_calls_test.go @@ -11,7 +11,6 @@ import ( "github.com/TBD54566975/ftl/backend/controller/encryption" "github.com/TBD54566975/ftl/backend/controller/pubsub" "github.com/TBD54566975/ftl/backend/controller/sql/sqltest" - "github.com/TBD54566975/ftl/backend/controller/timeline" "github.com/TBD54566975/ftl/backend/libdal" "github.com/TBD54566975/ftl/internal/log" "github.com/TBD54566975/ftl/internal/model" @@ -24,8 +23,7 @@ func TestNoCallToAcquire(t *testing.T) { encryption, err := encryption.New(ctx, conn, encryption.NewBuilder()) assert.NoError(t, err) - timelineSvc := timeline.New(ctx, conn, encryption) - pubSub := pubsub.New(ctx, conn, encryption, optional.None[pubsub.AsyncCallListener](), timelineSvc) + pubSub := pubsub.New(ctx, conn, encryption, optional.None[pubsub.AsyncCallListener]()) dal := New(ctx, conn, encryption, pubSub, nil) _, _, err = dal.AcquireAsyncCall(ctx) diff --git a/backend/controller/dal/dal.go b/backend/controller/dal/dal.go index 1d314244d6..18e1daea6a 100644 --- a/backend/controller/dal/dal.go +++ b/backend/controller/dal/dal.go @@ -16,11 +16,11 @@ import ( dalsql "github.com/TBD54566975/ftl/backend/controller/dal/internal/sql" dalmodel "github.com/TBD54566975/ftl/backend/controller/dal/model" "github.com/TBD54566975/ftl/backend/controller/encryption" - "github.com/TBD54566975/ftl/backend/controller/encryption/api" "github.com/TBD54566975/ftl/backend/controller/leases/dbleaser" "github.com/TBD54566975/ftl/backend/controller/pubsub" "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" "github.com/TBD54566975/ftl/backend/libdal" + "github.com/TBD54566975/ftl/backend/timeline" "github.com/TBD54566975/ftl/internal/log" "github.com/TBD54566975/ftl/internal/maps" "github.com/TBD54566975/ftl/internal/model" @@ -373,21 +373,11 @@ func (d *DAL) SetDeploymentReplicas(ctx context.Context, key model.DeploymentKey return libdal.TranslatePGError(err) } } - var payload api.EncryptedTimelineColumn - err = d.encryption.EncryptJSON(map[string]interface{}{ - "prev_min_replicas": deployment.MinReplicas, - "min_replicas": minReplicas, - }, &payload) - if err != nil { - return fmt.Errorf("failed to encrypt payload for InsertDeploymentUpdatedEvent: %w", err) - } - err = tx.db.InsertTimelineDeploymentUpdatedEvent(ctx, dalsql.InsertTimelineDeploymentUpdatedEventParams{ - DeploymentKey: key, - Payload: payload, + timeline.Publish(ctx, timeline.DeploymentUpdated{ + DeploymentKey: key, + MinReplicas: minReplicas, + PrevMinReplicas: int(deployment.MinReplicas), }) - if err != nil { - return libdal.TranslatePGError(err) - } return nil } @@ -443,25 +433,16 @@ func (d *DAL) ReplaceDeployment(ctx context.Context, newDeploymentKey model.Depl } } - var payload api.EncryptedTimelineColumn - err = d.encryption.EncryptJSON(map[string]any{ - "min_replicas": int32(minReplicas), - "replaced": replacedDeploymentKey, - }, &payload) - if err != nil { - return fmt.Errorf("replace deployment failed to encrypt payload: %w", err) - } - - err = tx.db.InsertTimelineDeploymentCreatedEvent(ctx, dalsql.InsertTimelineDeploymentCreatedEventParams{ - DeploymentKey: newDeploymentKey, - Language: newDeployment.Language, - ModuleName: newDeployment.ModuleName, - Payload: payload, + timeline.Publish(ctx, timeline.DeploymentCreated{ + DeploymentKey: newDeploymentKey, + Language: newDeployment.Language, + ModuleName: newDeployment.ModuleName, + MinReplicas: minReplicas, + ReplacedDeployment: replacedDeploymentKey, }) if err != nil { return fmt.Errorf("replace deployment failed to create event: %w", libdal.TranslatePGError(err)) } - return nil } diff --git a/backend/controller/dal/dal_test.go b/backend/controller/dal/dal_test.go index 9943ab676a..4209160e69 100644 --- a/backend/controller/dal/dal_test.go +++ b/backend/controller/dal/dal_test.go @@ -12,7 +12,6 @@ import ( "golang.org/x/sync/errgroup" "github.com/TBD54566975/ftl/backend/controller/artefacts" - "github.com/TBD54566975/ftl/backend/controller/timeline" dalmodel "github.com/TBD54566975/ftl/backend/controller/dal/model" "github.com/TBD54566975/ftl/backend/controller/encryption" @@ -31,8 +30,7 @@ func TestDAL(t *testing.T) { encryption, err := encryption.New(ctx, conn, encryption.NewBuilder()) assert.NoError(t, err) - timelineSrv := timeline.New(ctx, conn, encryption) - pubSub := pubsub.New(ctx, conn, encryption, optional.None[pubsub.AsyncCallListener](), timelineSrv) + pubSub := pubsub.New(ctx, conn, encryption, optional.None[pubsub.AsyncCallListener]()) dal := New(ctx, conn, encryption, pubSub, artefacts.NewForTesting()) var testContent = bytes.Repeat([]byte("sometestcontentthatislongerthanthereadbuffer"), 100) @@ -191,8 +189,7 @@ func TestCreateArtefactConflict(t *testing.T) { encryption, err := encryption.New(ctx, conn, encryption.NewBuilder()) assert.NoError(t, err) - timelineSrv := timeline.New(ctx, conn, encryption) - pubSub := pubsub.New(ctx, conn, encryption, optional.None[pubsub.AsyncCallListener](), timelineSrv) + pubSub := pubsub.New(ctx, conn, encryption, optional.None[pubsub.AsyncCallListener]()) dal := New(ctx, conn, encryption, pubSub, artefacts.NewForTesting()) diff --git a/backend/controller/dal/internal/sql/deployment_queries.sql.go b/backend/controller/dal/internal/sql/deployment_queries.sql.go deleted file mode 100644 index 34a7b55897..0000000000 --- a/backend/controller/dal/internal/sql/deployment_queries.sql.go +++ /dev/null @@ -1,89 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.27.0 -// source: deployment_queries.sql - -package sql - -import ( - "context" - - "github.com/TBD54566975/ftl/backend/controller/encryption/api" - "github.com/TBD54566975/ftl/internal/model" -) - -const insertTimelineDeploymentCreatedEvent = `-- name: InsertTimelineDeploymentCreatedEvent :exec -INSERT INTO timeline ( - deployment_id, - type, - custom_key_1, - custom_key_2, - payload -) -VALUES ( - ( - SELECT id - FROM deployments - WHERE deployments.key = $1::deployment_key - ), - 'deployment_created', - $2::TEXT, - $3::TEXT, - $4 -) -` - -type InsertTimelineDeploymentCreatedEventParams struct { - DeploymentKey model.DeploymentKey - Language string - ModuleName string - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelineDeploymentCreatedEvent(ctx context.Context, arg InsertTimelineDeploymentCreatedEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelineDeploymentCreatedEvent, - arg.DeploymentKey, - arg.Language, - arg.ModuleName, - arg.Payload, - ) - return err -} - -const insertTimelineDeploymentUpdatedEvent = `-- name: InsertTimelineDeploymentUpdatedEvent :exec -INSERT INTO timeline ( - deployment_id, - type, - custom_key_1, - custom_key_2, - payload -) -VALUES ( - ( - SELECT id - FROM deployments - WHERE deployments.key = $1::deployment_key - ), - 'deployment_updated', - $2::TEXT, - $3::TEXT, - $4 -) -` - -type InsertTimelineDeploymentUpdatedEventParams struct { - DeploymentKey model.DeploymentKey - Language string - ModuleName string - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelineDeploymentUpdatedEvent(ctx context.Context, arg InsertTimelineDeploymentUpdatedEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelineDeploymentUpdatedEvent, - arg.DeploymentKey, - arg.Language, - arg.ModuleName, - arg.Payload, - ) - return err -} diff --git a/backend/controller/dal/internal/sql/querier.go b/backend/controller/dal/internal/sql/querier.go index 5ce2f4257a..1ca5b126da 100644 --- a/backend/controller/dal/internal/sql/querier.go +++ b/backend/controller/dal/internal/sql/querier.go @@ -61,8 +61,6 @@ type Querier interface { GetTopicEvent(ctx context.Context, dollar_1 int64) (TopicEvent, error) GetZombieAsyncCalls(ctx context.Context, limit int32) ([]AsyncCall, error) InsertSubscriber(ctx context.Context, arg InsertSubscriberParams) error - InsertTimelineDeploymentCreatedEvent(ctx context.Context, arg InsertTimelineDeploymentCreatedEventParams) error - InsertTimelineDeploymentUpdatedEvent(ctx context.Context, arg InsertTimelineDeploymentUpdatedEventParams) error // Mark any controller entries that haven't been updated recently as dead. KillStaleControllers(ctx context.Context, timeout sqltypes.Duration) (int64, error) KillStaleRunners(ctx context.Context, timeout sqltypes.Duration) (int64, error) diff --git a/backend/controller/deployment_logs.go b/backend/controller/deployment_logs.go index 48a90b1ce9..d79a8b532e 100644 --- a/backend/controller/deployment_logs.go +++ b/backend/controller/deployment_logs.go @@ -7,17 +7,16 @@ import ( "github.com/alecthomas/types/optional" - "github.com/TBD54566975/ftl/backend/controller/timeline" + "github.com/TBD54566975/ftl/backend/timeline" "github.com/TBD54566975/ftl/internal/log" "github.com/TBD54566975/ftl/internal/model" ) var _ log.Sink = (*deploymentLogsSink)(nil) -func newDeploymentLogsSink(ctx context.Context, timeline *timeline.Service) *deploymentLogsSink { +func newDeploymentLogsSink(ctx context.Context) *deploymentLogsSink { sink := &deploymentLogsSink{ logQueue: make(chan log.Entry, 10000), - timeline: timeline, } // Process logs in background @@ -28,7 +27,6 @@ func newDeploymentLogsSink(ctx context.Context, timeline *timeline.Service) *dep type deploymentLogsSink struct { logQueue chan log.Entry - timeline *timeline.Service } // Log implements Sink @@ -71,7 +69,7 @@ func (d *deploymentLogsSink) processLogs(ctx context.Context) { errorStr = optional.Some(entry.Error.Error()) } - d.timeline.EnqueueEvent(ctx, &timeline.Log{ + timeline.Publish(ctx, &timeline.Log{ RequestKey: request, DeploymentKey: deployment, Time: entry.Time, diff --git a/backend/controller/pubsub/internal/dal/dal.go b/backend/controller/pubsub/internal/dal/dal.go index 7af65aecfe..e4d530f250 100644 --- a/backend/controller/pubsub/internal/dal/dal.go +++ b/backend/controller/pubsub/internal/dal/dal.go @@ -14,8 +14,8 @@ import ( "github.com/TBD54566975/ftl/backend/controller/observability" dalsql "github.com/TBD54566975/ftl/backend/controller/pubsub/internal/sql" "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" - "github.com/TBD54566975/ftl/backend/controller/timeline" "github.com/TBD54566975/ftl/backend/libdal" + "github.com/TBD54566975/ftl/backend/timeline" "github.com/TBD54566975/ftl/internal/log" "github.com/TBD54566975/ftl/internal/model" "github.com/TBD54566975/ftl/internal/rpc" @@ -98,7 +98,7 @@ func (d *DAL) GetSubscriptionsNeedingUpdate(ctx context.Context) ([]model.Subscr }), nil } -func (d *DAL) ProgressSubscriptions(ctx context.Context, eventConsumptionDelay time.Duration, timelineSvc *timeline.Service) (count int, err error) { +func (d *DAL) ProgressSubscriptions(ctx context.Context, eventConsumptionDelay time.Duration) (count int, err error) { tx, err := d.Begin(ctx) if err != nil { return 0, fmt.Errorf("failed to begin transaction: %w", err) @@ -118,7 +118,7 @@ func (d *DAL) ProgressSubscriptions(ctx context.Context, eventConsumptionDelay t for _, subscription := range subs { now := time.Now().UTC() enqueueTimelineEvent := func(destVerb optional.Option[schema.RefKey], err optional.Option[string]) { - timelineSvc.EnqueueEvent(ctx, &timeline.PubSubConsume{ + timeline.Publish(ctx, &timeline.PubSubConsume{ DeploymentKey: subscription.DeploymentKey, RequestKey: subscription.RequestKey, Time: now, diff --git a/backend/controller/pubsub/service.go b/backend/controller/pubsub/service.go index 8e98b4f1a4..66b9202fc9 100644 --- a/backend/controller/pubsub/service.go +++ b/backend/controller/pubsub/service.go @@ -13,7 +13,6 @@ import ( "github.com/TBD54566975/ftl/backend/controller/encryption" "github.com/TBD54566975/ftl/backend/controller/pubsub/internal/dal" "github.com/TBD54566975/ftl/backend/controller/scheduledtask" - "github.com/TBD54566975/ftl/backend/controller/timeline" "github.com/TBD54566975/ftl/backend/libdal" "github.com/TBD54566975/ftl/internal/log" "github.com/TBD54566975/ftl/internal/model" @@ -40,15 +39,13 @@ type Service struct { dal *dal.DAL asyncCallListener optional.Option[AsyncCallListener] eventPublished chan struct{} - timelineSvc *timeline.Service } -func New(ctx context.Context, conn libdal.Connection, encryption *encryption.Service, asyncCallListener optional.Option[AsyncCallListener], timeline *timeline.Service) *Service { +func New(ctx context.Context, conn libdal.Connection, encryption *encryption.Service, asyncCallListener optional.Option[AsyncCallListener]) *Service { m := &Service{ dal: dal.New(conn, encryption), asyncCallListener: asyncCallListener, eventPublished: make(chan struct{}), - timelineSvc: timeline, } go m.poll(ctx) return m @@ -93,7 +90,7 @@ func (s *Service) poll(ctx context.Context) { } func (s *Service) progressSubscriptions(ctx context.Context) error { - count, err := s.dal.ProgressSubscriptions(ctx, eventConsumptionDelay, s.timelineSvc) + count, err := s.dal.ProgressSubscriptions(ctx, eventConsumptionDelay) if err != nil { return fmt.Errorf("progress subscriptions: %w", err) } diff --git a/backend/controller/timeline/events_async.go b/backend/controller/timeline/events_async.go deleted file mode 100644 index def3696ddc..0000000000 --- a/backend/controller/timeline/events_async.go +++ /dev/null @@ -1,87 +0,0 @@ -package timeline - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/alecthomas/types/optional" - - ftlencryption "github.com/TBD54566975/ftl/backend/controller/encryption/api" - "github.com/TBD54566975/ftl/backend/controller/timeline/internal/sql" - "github.com/TBD54566975/ftl/backend/libdal" - "github.com/TBD54566975/ftl/internal/model" - "github.com/TBD54566975/ftl/internal/schema" -) - -type AsyncExecuteEvent struct { - ID int64 - Duration time.Duration - AsyncExecute -} - -func (e *AsyncExecuteEvent) GetID() int64 { return e.ID } -func (e *AsyncExecuteEvent) event() {} - -type AsyncExecuteEventType string - -const ( - AsyncExecuteEventTypeUnkown AsyncExecuteEventType = "unknown" - AsyncExecuteEventTypeCron AsyncExecuteEventType = "cron" - AsyncExecuteEventTypePubSub AsyncExecuteEventType = "pubsub" -) - -type AsyncExecute struct { - DeploymentKey model.DeploymentKey - RequestKey optional.Option[string] - EventType AsyncExecuteEventType - Verb schema.Ref - Time time.Time - Error optional.Option[string] -} - -func (e *AsyncExecute) toEvent() (Event, error) { //nolint:unparam - return &AsyncExecuteEvent{ - AsyncExecute: *e, - Duration: time.Since(e.Time), - }, nil -} - -type eventAsyncExecuteJSON struct { - DurationMS int64 `json:"duration_ms"` - EventType AsyncExecuteEventType `json:"event_type"` - Error optional.Option[string] `json:"error,omitempty"` -} - -func (s *Service) insertAsyncExecuteEvent(ctx context.Context, querier sql.Querier, event *AsyncExecuteEvent) error { - asyncJSON := eventAsyncExecuteJSON{ - DurationMS: event.Duration.Milliseconds(), - EventType: event.EventType, - Error: event.Error, - } - - data, err := json.Marshal(asyncJSON) - if err != nil { - return fmt.Errorf("failed to marshal async execute event: %w", err) - } - - var payload ftlencryption.EncryptedTimelineColumn - err = s.encryption.EncryptJSON(json.RawMessage(data), &payload) - if err != nil { - return fmt.Errorf("failed to encrypt cron JSON: %w", err) - } - - err = libdal.TranslatePGError(querier.InsertTimelineAsyncExecuteEvent(ctx, sql.InsertTimelineAsyncExecuteEventParams{ - DeploymentKey: event.DeploymentKey, - RequestKey: event.RequestKey, - TimeStamp: event.Time, - Module: event.Verb.Module, - Verb: event.Verb.Name, - Payload: payload, - })) - if err != nil { - return fmt.Errorf("failed to insert async execute event: %w", err) - } - return err -} diff --git a/backend/controller/timeline/events_call.go b/backend/controller/timeline/events_call.go deleted file mode 100644 index b470eea0b2..0000000000 --- a/backend/controller/timeline/events_call.go +++ /dev/null @@ -1,184 +0,0 @@ -package timeline - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "time" - - "github.com/alecthomas/types/either" - "github.com/alecthomas/types/optional" - - ftlencryption "github.com/TBD54566975/ftl/backend/controller/encryption/api" - "github.com/TBD54566975/ftl/backend/controller/timeline/internal/sql" - "github.com/TBD54566975/ftl/backend/libdal" - ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" - "github.com/TBD54566975/ftl/internal/model" - "github.com/TBD54566975/ftl/internal/schema" -) - -type CallEvent struct { - ID int64 - DeploymentKey model.DeploymentKey - RequestKey optional.Option[model.RequestKey] - ParentRequestKey optional.Option[model.RequestKey] - Time time.Time - SourceVerb optional.Option[schema.Ref] - DestVerb schema.Ref - Duration time.Duration - Request json.RawMessage - Response json.RawMessage - Error optional.Option[string] - Stack optional.Option[string] -} - -func (e *CallEvent) GetID() int64 { return e.ID } -func (e *CallEvent) event() {} - -type eventCallJSON struct { - DurationMS int64 `json:"duration_ms"` - Request json.RawMessage `json:"request"` - Response json.RawMessage `json:"response"` - Error optional.Option[string] `json:"error,omitempty"` - Stack optional.Option[string] `json:"stack,omitempty"` -} - -type Call struct { - DeploymentKey model.DeploymentKey - RequestKey model.RequestKey - ParentRequestKey optional.Option[model.RequestKey] - StartTime time.Time - DestVerb *schema.Ref - Callers []*schema.Ref - Request *ftlv1.CallRequest - Response either.Either[*ftlv1.CallResponse, error] -} - -func (c *Call) toEvent() (Event, error) { return callToCallEvent(c), nil } //nolint:unparam - -func (s *Service) insertCallEvent(ctx context.Context, querier sql.Querier, callEvent *CallEvent) error { - var sourceModule, sourceVerb optional.Option[string] - if sr, ok := callEvent.SourceVerb.Get(); ok { - sourceModule, sourceVerb = optional.Some(sr.Module), optional.Some(sr.Name) - } - - var requestKey optional.Option[string] - if rn, ok := callEvent.RequestKey.Get(); ok { - requestKey = optional.Some(rn.String()) - } - - var parentRequestKey optional.Option[string] - if pr, ok := callEvent.ParentRequestKey.Get(); ok { - parentRequestKey = optional.Some(pr.String()) - } - - callJSON := eventCallJSON{ - DurationMS: callEvent.Duration.Milliseconds(), - Request: callEvent.Request, - Response: callEvent.Response, - Error: callEvent.Error, - Stack: callEvent.Stack, - } - - data, err := json.Marshal(callJSON) - if err != nil { - return fmt.Errorf("failed to marshal call event: %w", err) - } - - var payload ftlencryption.EncryptedTimelineColumn - err = s.encryption.EncryptJSON(json.RawMessage(data), &payload) - if err != nil { - return fmt.Errorf("failed to encrypt call event: %w", err) - } - - err = libdal.TranslatePGError(querier.InsertTimelineCallEvent(ctx, sql.InsertTimelineCallEventParams{ - DeploymentKey: callEvent.DeploymentKey, - RequestKey: requestKey, - ParentRequestKey: parentRequestKey, - TimeStamp: callEvent.Time, - SourceModule: sourceModule, - SourceVerb: sourceVerb, - DestModule: callEvent.DestVerb.Module, - DestVerb: callEvent.DestVerb.Name, - Payload: payload, - })) - if err != nil { - return fmt.Errorf("failed to insert call event: %w", err) - } - return nil -} - -func callToCallEvent(call *Call) *CallEvent { - var sourceVerb optional.Option[schema.Ref] - if len(call.Callers) > 0 { - sourceVerb = optional.Some(*call.Callers[0]) - } - - var errorStr optional.Option[string] - var stack optional.Option[string] - var responseBody []byte - - switch response := call.Response.(type) { - case either.Left[*ftlv1.CallResponse, error]: - resp := response.Get() - responseBody = resp.GetBody() - if callError := resp.GetError(); callError != nil { - errorStr = optional.Some(callError.Message) - stack = optional.Ptr(callError.Stack) - } - case either.Right[*ftlv1.CallResponse, error]: - callError := response.Get() - errorStr = optional.Some(callError.Error()) - } - - return &CallEvent{ - Time: call.StartTime, - DeploymentKey: call.DeploymentKey, - RequestKey: optional.Some(call.RequestKey), - ParentRequestKey: call.ParentRequestKey, - Duration: time.Since(call.StartTime), - SourceVerb: sourceVerb, - DestVerb: *call.DestVerb, - Request: call.Request.GetBody(), - Response: responseBody, - Error: errorStr, - Stack: stack, - } -} - -func CallEventToCallForTesting(event *CallEvent) *Call { - var response either.Either[*ftlv1.CallResponse, error] - if eventErr, ok := event.Error.Get(); ok { - response = either.RightOf[*ftlv1.CallResponse](errors.New(eventErr)) - } else { - response = either.LeftOf[error](&ftlv1.CallResponse{ - Response: &ftlv1.CallResponse_Body{ - Body: event.Response, - }, - }) - } - - var requestKey model.RequestKey - if key, ok := event.RequestKey.Get(); ok { - requestKey = key - } else { - requestKey = model.RequestKey{} - } - - callers := []*schema.Ref{} - if ref, ok := event.SourceVerb.Get(); ok { - callers = []*schema.Ref{&ref} - } - - return &Call{ - DeploymentKey: event.DeploymentKey, - RequestKey: requestKey, - ParentRequestKey: event.ParentRequestKey, - StartTime: event.Time, - DestVerb: &event.DestVerb, - Callers: callers, - Request: &ftlv1.CallRequest{Body: event.Request}, - Response: response, - } -} diff --git a/backend/controller/timeline/events_cron.go b/backend/controller/timeline/events_cron.go deleted file mode 100644 index 40394a3b2d..0000000000 --- a/backend/controller/timeline/events_cron.go +++ /dev/null @@ -1,81 +0,0 @@ -package timeline - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/alecthomas/types/optional" - - ftlencryption "github.com/TBD54566975/ftl/backend/controller/encryption/api" - "github.com/TBD54566975/ftl/backend/controller/timeline/internal/sql" - "github.com/TBD54566975/ftl/backend/libdal" - "github.com/TBD54566975/ftl/internal/model" - "github.com/TBD54566975/ftl/internal/schema" -) - -type CronScheduledEvent struct { - ID int64 - Duration time.Duration - CronScheduled -} - -func (e *CronScheduledEvent) GetID() int64 { return e.ID } -func (e *CronScheduledEvent) event() {} - -type CronScheduled struct { - DeploymentKey model.DeploymentKey - Verb schema.Ref - - Time time.Time - ScheduledAt time.Time - Schedule string - Error optional.Option[string] -} - -func (e *CronScheduled) toEvent() (Event, error) { //nolint:unparam - return &CronScheduledEvent{ - CronScheduled: *e, - Duration: time.Since(e.Time), - }, nil -} - -type eventCronScheduledJSON struct { - DurationMS int64 `json:"duration_ms"` - ScheduledAt time.Time `json:"scheduled_at"` - Schedule string `json:"schedule"` - Error optional.Option[string] `json:"error,omitempty"` -} - -func (s *Service) insertCronScheduledEvent(ctx context.Context, querier sql.Querier, event *CronScheduledEvent) error { - cronJSON := eventCronScheduledJSON{ - DurationMS: event.Duration.Milliseconds(), - ScheduledAt: event.ScheduledAt, - Schedule: event.Schedule, - Error: event.Error, - } - - data, err := json.Marshal(cronJSON) - if err != nil { - return fmt.Errorf("failed to marshal cron JSON: %w", err) - } - - var payload ftlencryption.EncryptedTimelineColumn - err = s.encryption.EncryptJSON(json.RawMessage(data), &payload) - if err != nil { - return fmt.Errorf("failed to encrypt cron JSON: %w", err) - } - - err = libdal.TranslatePGError(querier.InsertTimelineCronScheduledEvent(ctx, sql.InsertTimelineCronScheduledEventParams{ - DeploymentKey: event.DeploymentKey, - TimeStamp: event.Time, - Module: event.Verb.Module, - Verb: event.Verb.Name, - Payload: payload, - })) - if err != nil { - return fmt.Errorf("failed to insert cron event: %w", err) - } - return err -} diff --git a/backend/controller/timeline/events_deployment.go b/backend/controller/timeline/events_deployment.go deleted file mode 100644 index 723da92858..0000000000 --- a/backend/controller/timeline/events_deployment.go +++ /dev/null @@ -1,43 +0,0 @@ -package timeline - -import ( - "time" - - "github.com/alecthomas/types/optional" - - "github.com/TBD54566975/ftl/internal/model" -) - -type DeploymentCreatedEvent struct { - ID int64 - DeploymentKey model.DeploymentKey - Time time.Time - Language string - ModuleName string - MinReplicas int - ReplacedDeployment optional.Option[model.DeploymentKey] -} - -func (e *DeploymentCreatedEvent) GetID() int64 { return e.ID } -func (e *DeploymentCreatedEvent) event() {} - -type eventDeploymentUpdatedJSON struct { - MinReplicas int `json:"min_replicas"` - PrevMinReplicas int `json:"prev_min_replicas"` -} - -type DeploymentUpdatedEvent struct { - ID int64 - DeploymentKey model.DeploymentKey - Time time.Time - MinReplicas int - PrevMinReplicas int -} - -func (e *DeploymentUpdatedEvent) GetID() int64 { return e.ID } -func (e *DeploymentUpdatedEvent) event() {} - -type eventDeploymentCreatedJSON struct { - MinReplicas int `json:"min_replicas"` - ReplacedDeployment optional.Option[model.DeploymentKey] `json:"replaced,omitempty"` -} diff --git a/backend/controller/timeline/events_ingress.go b/backend/controller/timeline/events_ingress.go deleted file mode 100644 index 325b4684fb..0000000000 --- a/backend/controller/timeline/events_ingress.go +++ /dev/null @@ -1,144 +0,0 @@ -package timeline - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/alecthomas/types/optional" - - ftlencryption "github.com/TBD54566975/ftl/backend/controller/encryption/api" - "github.com/TBD54566975/ftl/backend/controller/timeline/internal/sql" - "github.com/TBD54566975/ftl/backend/libdal" - "github.com/TBD54566975/ftl/internal/log" - "github.com/TBD54566975/ftl/internal/model" - "github.com/TBD54566975/ftl/internal/schema" -) - -type IngressEvent struct { - ID int64 - DeploymentKey model.DeploymentKey - RequestKey optional.Option[model.RequestKey] - Verb schema.Ref - Method string - Path string - - StatusCode int - Time time.Time - Duration time.Duration - Request json.RawMessage - RequestHeader json.RawMessage - Response json.RawMessage - ResponseHeader json.RawMessage - Error optional.Option[string] -} - -func (e *IngressEvent) GetID() int64 { return e.ID } -func (e *IngressEvent) event() {} - -type eventIngressJSON struct { - DurationMS int64 `json:"duration_ms"` - Method string `json:"method"` - Path string `json:"path"` - StatusCode int `json:"status_code"` - Request json.RawMessage `json:"request"` - RequestHeader json.RawMessage `json:"req_header"` - Response json.RawMessage `json:"response"` - ResponseHeader json.RawMessage `json:"resp_header"` - Error optional.Option[string] `json:"error,omitempty"` -} - -type Ingress struct { - DeploymentKey model.DeploymentKey - RequestKey model.RequestKey - StartTime time.Time - Verb *schema.Ref - RequestMethod string - RequestPath string - RequestHeaders http.Header - ResponseStatus int - ResponseHeaders http.Header - RequestBody []byte - ResponseBody []byte - Error optional.Option[string] -} - -func (ingress *Ingress) toEvent() (Event, error) { - requestBody := ingress.RequestBody - if len(requestBody) == 0 { - requestBody = []byte("{}") - } - - responseBody := ingress.ResponseBody - if len(responseBody) == 0 { - responseBody = []byte("{}") - } - - reqHeaderBytes, err := json.Marshal(ingress.RequestHeaders) - if err != nil { - return nil, fmt.Errorf("failed to marshal request header: %w", err) - } - - respHeaderBytes, err := json.Marshal(ingress.ResponseHeaders) - if err != nil { - return nil, fmt.Errorf("failed to marshal response header: %w", err) - } - return &IngressEvent{ - DeploymentKey: ingress.DeploymentKey, - RequestKey: optional.Some(ingress.RequestKey), - Verb: *ingress.Verb, - Method: ingress.RequestMethod, - Path: ingress.RequestPath, - StatusCode: ingress.ResponseStatus, - Time: ingress.StartTime, - Duration: time.Since(ingress.StartTime), - Request: requestBody, - RequestHeader: reqHeaderBytes, - Response: responseBody, - ResponseHeader: respHeaderBytes, - Error: ingress.Error, - }, nil -} - -func (s *Service) insertHTTPIngress(ctx context.Context, querier sql.Querier, ingress *IngressEvent) error { - ingressJSON := eventIngressJSON{ - DurationMS: ingress.Duration.Milliseconds(), - Method: ingress.Method, - Path: ingress.Path, - StatusCode: ingress.StatusCode, - Request: ingress.Request, - RequestHeader: ingress.RequestHeader, - Response: ingress.Response, - ResponseHeader: ingress.ResponseHeader, - Error: ingress.Error, - } - - data, err := json.Marshal(ingressJSON) - if err != nil { - return fmt.Errorf("failed to marshal ingress JSON: %w", err) - } - - var payload ftlencryption.EncryptedTimelineColumn - err = s.encryption.EncryptJSON(json.RawMessage(data), &payload) - if err != nil { - return fmt.Errorf("failed to encrypt ingress payload: %w", err) - } - - log.FromContext(ctx).Debugf("Inserting ingress event for %s %s", ingress.RequestKey, ingress.Path) - - err = libdal.TranslatePGError(querier.InsertTimelineIngressEvent(ctx, sql.InsertTimelineIngressEventParams{ - DeploymentKey: ingress.DeploymentKey, - RequestKey: optional.Some(ingress.RequestKey.String()), - TimeStamp: ingress.Time, - Module: ingress.Verb.Module, - Verb: ingress.Verb.Name, - IngressType: "http", - Payload: payload, - })) - if err != nil { - return fmt.Errorf("failed to insert ingress event: %w", err) - } - return nil -} diff --git a/backend/controller/timeline/events_log.go b/backend/controller/timeline/events_log.go deleted file mode 100644 index 67952382b1..0000000000 --- a/backend/controller/timeline/events_log.go +++ /dev/null @@ -1,73 +0,0 @@ -package timeline - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/alecthomas/types/optional" - - ftlencryption "github.com/TBD54566975/ftl/backend/controller/encryption/api" - "github.com/TBD54566975/ftl/backend/controller/timeline/internal/sql" - "github.com/TBD54566975/ftl/backend/libdal" - "github.com/TBD54566975/ftl/internal/model" -) - -type Log struct { - DeploymentKey model.DeploymentKey - RequestKey optional.Option[model.RequestKey] - Time time.Time - Level int32 - Attributes map[string]string - Message string - Error optional.Option[string] -} - -func (l *Log) toEvent() (Event, error) { return &LogEvent{Log: *l}, nil } //nolint:unparam - -type LogEvent struct { - ID int64 - Log -} - -func (e *LogEvent) GetID() int64 { return e.ID } -func (e *LogEvent) event() {} - -type eventLogJSON struct { - Message string `json:"message"` - Attributes map[string]string `json:"attributes"` - Error optional.Option[string] `json:"error,omitempty"` -} - -func (s *Service) insertLogEvent(ctx context.Context, querier sql.Querier, log *LogEvent) error { - var requestKey optional.Option[string] - if name, ok := log.RequestKey.Get(); ok { - requestKey = optional.Some(name.String()) - } - - logJSON := eventLogJSON{ - Message: log.Message, - Attributes: log.Attributes, - Error: log.Error, - } - - data, err := json.Marshal(logJSON) - if err != nil { - return fmt.Errorf("failed to marshal log event: %w", err) - } - - var encryptedPayload ftlencryption.EncryptedTimelineColumn - err = s.encryption.EncryptJSON(json.RawMessage(data), &encryptedPayload) - if err != nil { - return fmt.Errorf("failed to encrypt log payload: %w", err) - } - - return libdal.TranslatePGError(querier.InsertTimelineLogEvent(ctx, sql.InsertTimelineLogEventParams{ - DeploymentKey: log.DeploymentKey, - RequestKey: requestKey, - TimeStamp: log.Time, - Level: log.Level, - Payload: encryptedPayload, - })) -} diff --git a/backend/controller/timeline/events_pubsub_consume.go b/backend/controller/timeline/events_pubsub_consume.go deleted file mode 100644 index 66904dfae0..0000000000 --- a/backend/controller/timeline/events_pubsub_consume.go +++ /dev/null @@ -1,87 +0,0 @@ -package timeline - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/alecthomas/types/optional" - - ftlencryption "github.com/TBD54566975/ftl/backend/controller/encryption/api" - "github.com/TBD54566975/ftl/backend/controller/timeline/internal/sql" - "github.com/TBD54566975/ftl/backend/libdal" - "github.com/TBD54566975/ftl/internal/model" - "github.com/TBD54566975/ftl/internal/schema" -) - -type PubSubConsumeEvent struct { - ID int64 - Duration time.Duration - PubSubConsume -} - -func (e *PubSubConsumeEvent) GetID() int64 { return e.ID } -func (e *PubSubConsumeEvent) event() {} - -type PubSubConsume struct { - DeploymentKey model.DeploymentKey - RequestKey optional.Option[string] - Time time.Time - DestVerb optional.Option[schema.RefKey] - Topic string - Error optional.Option[string] -} - -func (e *PubSubConsume) toEvent() (Event, error) { //nolint:unparam - return &PubSubConsumeEvent{ - PubSubConsume: *e, - Duration: time.Since(e.Time), - }, nil -} - -type eventPubSubConsumeJSON struct { - DurationMS int64 `json:"duration_ms"` - Topic string `json:"topic"` - Error optional.Option[string] `json:"error,omitempty"` -} - -func (s *Service) insertPubSubConsumeEvent(ctx context.Context, querier sql.Querier, event *PubSubConsumeEvent) error { - pubsubJSON := eventPubSubConsumeJSON{ - DurationMS: event.Duration.Milliseconds(), - Topic: event.Topic, - Error: event.Error, - } - - data, err := json.Marshal(pubsubJSON) - if err != nil { - return fmt.Errorf("failed to marshal pubsub event: %w", err) - } - - var payload ftlencryption.EncryptedTimelineColumn - err = s.encryption.EncryptJSON(json.RawMessage(data), &payload) - if err != nil { - return fmt.Errorf("failed to encrypt cron JSON: %w", err) - } - - destModule := optional.None[string]() - destVerb := optional.None[string]() - if dv, ok := event.DestVerb.Get(); ok { - destModule = optional.Some(dv.Module) - destVerb = optional.Some(dv.Name) - } - - err = libdal.TranslatePGError(querier.InsertTimelinePubsubConsumeEvent(ctx, sql.InsertTimelinePubsubConsumeEventParams{ - DeploymentKey: event.DeploymentKey, - RequestKey: event.RequestKey, - TimeStamp: event.Time, - DestModule: destModule, - DestVerb: destVerb, - Topic: event.Topic, - Payload: payload, - })) - if err != nil { - return fmt.Errorf("failed to insert pubsub consume event: %w", err) - } - return err -} diff --git a/backend/controller/timeline/events_pubsub_publish.go b/backend/controller/timeline/events_pubsub_publish.go deleted file mode 100644 index b3d2b072af..0000000000 --- a/backend/controller/timeline/events_pubsub_publish.go +++ /dev/null @@ -1,85 +0,0 @@ -package timeline - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/alecthomas/types/optional" - - ftlencryption "github.com/TBD54566975/ftl/backend/controller/encryption/api" - "github.com/TBD54566975/ftl/backend/controller/timeline/internal/sql" - "github.com/TBD54566975/ftl/backend/libdal" - deployment "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/deployment/v1" - "github.com/TBD54566975/ftl/internal/model" - "github.com/TBD54566975/ftl/internal/schema" -) - -type PubSubPublishEvent struct { - ID int64 - Duration time.Duration - Request json.RawMessage - PubSubPublish -} - -func (e *PubSubPublishEvent) GetID() int64 { return e.ID } -func (e *PubSubPublishEvent) event() {} - -type PubSubPublish struct { - DeploymentKey model.DeploymentKey - RequestKey optional.Option[string] - Time time.Time - SourceVerb schema.Ref - Topic string - Request *deployment.PublishEventRequest - Error optional.Option[string] -} - -func (e *PubSubPublish) toEvent() (Event, error) { //nolint:unparam - return &PubSubPublishEvent{ - PubSubPublish: *e, - Duration: time.Since(e.Time), - }, nil -} - -type eventPubSubPublishJSON struct { - DurationMS int64 `json:"duration_ms"` - Topic string `json:"topic"` - Request json.RawMessage `json:"request"` - Error optional.Option[string] `json:"error,omitempty"` -} - -func (s *Service) insertPubSubPublishEvent(ctx context.Context, querier sql.Querier, event *PubSubPublishEvent) error { - pubsubJSON := eventPubSubPublishJSON{ - DurationMS: event.Duration.Milliseconds(), - Topic: event.Topic, - Request: event.Request, - Error: event.Error, - } - - data, err := json.Marshal(pubsubJSON) - if err != nil { - return fmt.Errorf("failed to marshal pubsub event: %w", err) - } - - var payload ftlencryption.EncryptedTimelineColumn - err = s.encryption.EncryptJSON(json.RawMessage(data), &payload) - if err != nil { - return fmt.Errorf("failed to encrypt cron JSON: %w", err) - } - - err = libdal.TranslatePGError(querier.InsertTimelinePubsubPublishEvent(ctx, sql.InsertTimelinePubsubPublishEventParams{ - DeploymentKey: event.DeploymentKey, - RequestKey: event.RequestKey, - TimeStamp: event.Time, - SourceModule: event.SourceVerb.Module, - SourceVerb: event.SourceVerb.Name, - Topic: event.Topic, - Payload: payload, - })) - if err != nil { - return fmt.Errorf("failed to insert pubsub publish event: %w", err) - } - return err -} diff --git a/backend/controller/timeline/internal/sql/db.go b/backend/controller/timeline/internal/sql/db.go deleted file mode 100644 index 0e0973111c..0000000000 --- a/backend/controller/timeline/internal/sql/db.go +++ /dev/null @@ -1,31 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.27.0 - -package sql - -import ( - "context" - "database/sql" -) - -type DBTX interface { - ExecContext(context.Context, string, ...interface{}) (sql.Result, error) - PrepareContext(context.Context, string) (*sql.Stmt, error) - QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) - QueryRowContext(context.Context, string, ...interface{}) *sql.Row -} - -func New(db DBTX) *Queries { - return &Queries{db: db} -} - -type Queries struct { - db DBTX -} - -func (q *Queries) WithTx(tx *sql.Tx) *Queries { - return &Queries{ - db: tx, - } -} diff --git a/backend/controller/timeline/internal/sql/deployment_queries.sql b/backend/controller/timeline/internal/sql/deployment_queries.sql deleted file mode 100644 index 268a22047e..0000000000 --- a/backend/controller/timeline/internal/sql/deployment_queries.sql +++ /dev/null @@ -1,39 +0,0 @@ --- name: InsertTimelineDeploymentCreatedEvent :exec -INSERT INTO timeline ( - deployment_id, - type, - custom_key_1, - custom_key_2, - payload -) -VALUES ( - ( - SELECT id - FROM deployments - WHERE deployments.key = sqlc.arg('deployment_key')::deployment_key - ), - 'deployment_created', - sqlc.arg('language')::TEXT, - sqlc.arg('module_name')::TEXT, - sqlc.arg('payload') -); - --- name: InsertTimelineDeploymentUpdatedEvent :exec -INSERT INTO timeline ( - deployment_id, - type, - custom_key_1, - custom_key_2, - payload -) -VALUES ( - ( - SELECT id - FROM deployments - WHERE deployments.key = sqlc.arg('deployment_key')::deployment_key - ), - 'deployment_updated', - sqlc.arg('language')::TEXT, - sqlc.arg('module_name')::TEXT, - sqlc.arg('payload') -); diff --git a/backend/controller/timeline/internal/sql/deployment_queries.sql.go b/backend/controller/timeline/internal/sql/deployment_queries.sql.go deleted file mode 100644 index 34a7b55897..0000000000 --- a/backend/controller/timeline/internal/sql/deployment_queries.sql.go +++ /dev/null @@ -1,89 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.27.0 -// source: deployment_queries.sql - -package sql - -import ( - "context" - - "github.com/TBD54566975/ftl/backend/controller/encryption/api" - "github.com/TBD54566975/ftl/internal/model" -) - -const insertTimelineDeploymentCreatedEvent = `-- name: InsertTimelineDeploymentCreatedEvent :exec -INSERT INTO timeline ( - deployment_id, - type, - custom_key_1, - custom_key_2, - payload -) -VALUES ( - ( - SELECT id - FROM deployments - WHERE deployments.key = $1::deployment_key - ), - 'deployment_created', - $2::TEXT, - $3::TEXT, - $4 -) -` - -type InsertTimelineDeploymentCreatedEventParams struct { - DeploymentKey model.DeploymentKey - Language string - ModuleName string - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelineDeploymentCreatedEvent(ctx context.Context, arg InsertTimelineDeploymentCreatedEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelineDeploymentCreatedEvent, - arg.DeploymentKey, - arg.Language, - arg.ModuleName, - arg.Payload, - ) - return err -} - -const insertTimelineDeploymentUpdatedEvent = `-- name: InsertTimelineDeploymentUpdatedEvent :exec -INSERT INTO timeline ( - deployment_id, - type, - custom_key_1, - custom_key_2, - payload -) -VALUES ( - ( - SELECT id - FROM deployments - WHERE deployments.key = $1::deployment_key - ), - 'deployment_updated', - $2::TEXT, - $3::TEXT, - $4 -) -` - -type InsertTimelineDeploymentUpdatedEventParams struct { - DeploymentKey model.DeploymentKey - Language string - ModuleName string - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelineDeploymentUpdatedEvent(ctx context.Context, arg InsertTimelineDeploymentUpdatedEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelineDeploymentUpdatedEvent, - arg.DeploymentKey, - arg.Language, - arg.ModuleName, - arg.Payload, - ) - return err -} diff --git a/backend/controller/timeline/internal/sql/models.go b/backend/controller/timeline/internal/sql/models.go deleted file mode 100644 index 90b3f40fb4..0000000000 --- a/backend/controller/timeline/internal/sql/models.go +++ /dev/null @@ -1,77 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.27.0 - -package sql - -import ( - "database/sql/driver" - "fmt" - "time" - - "github.com/TBD54566975/ftl/backend/controller/encryption/api" - "github.com/alecthomas/types/optional" -) - -type EventType string - -const ( - EventTypeCall EventType = "call" - EventTypeLog EventType = "log" - EventTypeDeploymentCreated EventType = "deployment_created" - EventTypeDeploymentUpdated EventType = "deployment_updated" - EventTypeIngress EventType = "ingress" - EventTypeCronScheduled EventType = "cron_scheduled" - EventTypeAsyncExecute EventType = "async_execute" - EventTypePubsubPublish EventType = "pubsub_publish" - EventTypePubsubConsume EventType = "pubsub_consume" -) - -func (e *EventType) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = EventType(s) - case string: - *e = EventType(s) - default: - return fmt.Errorf("unsupported scan type for EventType: %T", src) - } - return nil -} - -type NullEventType struct { - EventType EventType - Valid bool // Valid is true if EventType is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullEventType) Scan(value interface{}) error { - if value == nil { - ns.EventType, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.EventType.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullEventType) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.EventType), nil -} - -type Timeline struct { - ID int64 - TimeStamp time.Time - DeploymentID int64 - RequestID optional.Option[int64] - Type EventType - CustomKey1 optional.Option[string] - CustomKey2 optional.Option[string] - CustomKey3 optional.Option[string] - CustomKey4 optional.Option[string] - Payload api.EncryptedTimelineColumn - ParentRequestID optional.Option[string] -} diff --git a/backend/controller/timeline/internal/sql/querier.go b/backend/controller/timeline/internal/sql/querier.go deleted file mode 100644 index ad15ffef67..0000000000 --- a/backend/controller/timeline/internal/sql/querier.go +++ /dev/null @@ -1,28 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.27.0 - -package sql - -import ( - "context" - - "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" -) - -type Querier interface { - DeleteOldTimelineEvents(ctx context.Context, timeout sqltypes.Duration, type_ EventType) (int64, error) - // This is a dummy query to ensure that the Timeline model is generated. - DummyQueryTimeline(ctx context.Context, id int64) (Timeline, error) - InsertTimelineAsyncExecuteEvent(ctx context.Context, arg InsertTimelineAsyncExecuteEventParams) error - InsertTimelineCallEvent(ctx context.Context, arg InsertTimelineCallEventParams) error - InsertTimelineCronScheduledEvent(ctx context.Context, arg InsertTimelineCronScheduledEventParams) error - InsertTimelineDeploymentCreatedEvent(ctx context.Context, arg InsertTimelineDeploymentCreatedEventParams) error - InsertTimelineDeploymentUpdatedEvent(ctx context.Context, arg InsertTimelineDeploymentUpdatedEventParams) error - InsertTimelineIngressEvent(ctx context.Context, arg InsertTimelineIngressEventParams) error - InsertTimelineLogEvent(ctx context.Context, arg InsertTimelineLogEventParams) error - InsertTimelinePubsubConsumeEvent(ctx context.Context, arg InsertTimelinePubsubConsumeEventParams) error - InsertTimelinePubsubPublishEvent(ctx context.Context, arg InsertTimelinePubsubPublishEventParams) error -} - -var _ Querier = (*Queries)(nil) diff --git a/backend/controller/timeline/internal/sql/queries.sql b/backend/controller/timeline/internal/sql/queries.sql deleted file mode 100644 index 2eee3cdfdf..0000000000 --- a/backend/controller/timeline/internal/sql/queries.sql +++ /dev/null @@ -1,186 +0,0 @@ --- name: InsertTimelineLogEvent :exec -INSERT INTO timeline ( - deployment_id, - request_id, - time_stamp, - custom_key_1, - type, - payload -) -VALUES ( - (SELECT id FROM deployments d WHERE d.key = sqlc.arg('deployment_key')::deployment_key LIMIT 1), - ( - CASE - WHEN sqlc.narg('request_key')::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = sqlc.narg('request_key')::TEXT LIMIT 1) - END - ), - sqlc.arg('time_stamp')::TIMESTAMPTZ, - sqlc.arg('level')::INT, - 'log', - sqlc.arg('payload') -); - --- name: InsertTimelineCallEvent :exec -INSERT INTO timeline ( - deployment_id, - request_id, - parent_request_id, - time_stamp, - type, - custom_key_1, - custom_key_2, - custom_key_3, - custom_key_4, - payload -) -VALUES ( - (SELECT id FROM deployments WHERE deployments.key = sqlc.arg('deployment_key')::deployment_key), - (CASE - WHEN sqlc.narg('request_key')::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = sqlc.narg('request_key')::TEXT) - END), - (CASE - WHEN sqlc.narg('parent_request_key')::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = sqlc.narg('parent_request_key')::TEXT) - END), - sqlc.arg('time_stamp')::TIMESTAMPTZ, - 'call', - sqlc.narg('source_module')::TEXT, - sqlc.narg('source_verb')::TEXT, - sqlc.arg('dest_module')::TEXT, - sqlc.arg('dest_verb')::TEXT, - sqlc.arg('payload') -); - --- name: InsertTimelineIngressEvent :exec -INSERT INTO timeline ( - deployment_id, - request_id, - time_stamp, - type, - custom_key_1, - custom_key_2, - custom_key_3, - payload -) -VALUES ( - (SELECT id FROM deployments d WHERE d.key = sqlc.arg('deployment_key')::deployment_key LIMIT 1), - ( - CASE - WHEN sqlc.narg('request_key')::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = sqlc.narg('request_key')::TEXT LIMIT 1) - END - ), - sqlc.arg('time_stamp')::TIMESTAMPTZ, - 'ingress', - sqlc.arg('module')::TEXT, - sqlc.arg('verb')::TEXT, - sqlc.arg('ingress_type')::TEXT, - sqlc.arg('payload') -); - --- name: InsertTimelineCronScheduledEvent :exec -INSERT INTO timeline ( - deployment_id, - time_stamp, - type, - custom_key_1, - custom_key_2, - payload -) -VALUES ( - (SELECT id FROM deployments d WHERE d.key = sqlc.arg('deployment_key')::deployment_key LIMIT 1), - sqlc.arg('time_stamp')::TIMESTAMPTZ, - 'cron_scheduled', - sqlc.arg('module')::TEXT, - sqlc.arg('verb')::TEXT, - sqlc.arg('payload') -); - --- name: InsertTimelineAsyncExecuteEvent :exec -INSERT INTO timeline ( - deployment_id, - request_id, - time_stamp, - type, - custom_key_1, - custom_key_2, - payload -) -VALUES ( - (SELECT id FROM deployments d WHERE d.key = sqlc.arg('deployment_key')::deployment_key LIMIT 1), - (CASE - WHEN sqlc.narg('request_key')::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = sqlc.narg('request_key')::TEXT) - END), - sqlc.arg('time_stamp')::TIMESTAMPTZ, - 'async_execute', - sqlc.arg('module')::TEXT, - sqlc.arg('verb')::TEXT, - sqlc.arg('payload') -); - --- name: InsertTimelinePubsubPublishEvent :exec -INSERT INTO timeline ( - deployment_id, - request_id, - time_stamp, - type, - custom_key_1, - custom_key_2, - custom_key_3, - payload -) -VALUES ( - (SELECT id FROM deployments d WHERE d.key = sqlc.arg('deployment_key')::deployment_key LIMIT 1), - (CASE - WHEN sqlc.narg('request_key')::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = sqlc.narg('request_key')::TEXT) - END), - sqlc.arg('time_stamp')::TIMESTAMPTZ, - 'pubsub_publish', - sqlc.arg('source_module')::TEXT, - sqlc.arg('source_verb')::TEXT, - sqlc.arg('topic')::TEXT, - sqlc.arg('payload') -); - --- name: InsertTimelinePubsubConsumeEvent :exec -INSERT INTO timeline ( - deployment_id, - request_id, - time_stamp, - type, - custom_key_1, - custom_key_2, - custom_key_3, - payload -) -VALUES ( - (SELECT id FROM deployments d WHERE d.key = sqlc.arg('deployment_key')::deployment_key LIMIT 1), - (CASE - WHEN sqlc.narg('request_key')::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = sqlc.narg('request_key')::TEXT) - END), - sqlc.arg('time_stamp')::TIMESTAMPTZ, - 'pubsub_consume', - sqlc.narg('dest_module')::TEXT, - sqlc.narg('dest_verb')::TEXT, - sqlc.arg('topic')::TEXT, - sqlc.arg('payload') -); - --- name: DeleteOldTimelineEvents :one -WITH deleted AS ( - DELETE FROM timeline - WHERE time_stamp < (NOW() AT TIME ZONE 'utc') - sqlc.arg('timeout')::INTERVAL - AND type = sqlc.arg('type') - RETURNING 1 -) -SELECT COUNT(*) -FROM deleted; - --- name: DummyQueryTimeline :one --- This is a dummy query to ensure that the Timeline model is generated. -SELECT * FROM timeline WHERE id = @id; diff --git a/backend/controller/timeline/internal/sql/queries.sql.go b/backend/controller/timeline/internal/sql/queries.sql.go deleted file mode 100644 index 6ea635a67f..0000000000 --- a/backend/controller/timeline/internal/sql/queries.sql.go +++ /dev/null @@ -1,393 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.27.0 -// source: queries.sql - -package sql - -import ( - "context" - "time" - - "github.com/TBD54566975/ftl/backend/controller/encryption/api" - "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" - "github.com/TBD54566975/ftl/internal/model" - "github.com/alecthomas/types/optional" -) - -const deleteOldTimelineEvents = `-- name: DeleteOldTimelineEvents :one -WITH deleted AS ( - DELETE FROM timeline - WHERE time_stamp < (NOW() AT TIME ZONE 'utc') - $1::INTERVAL - AND type = $2 - RETURNING 1 -) -SELECT COUNT(*) -FROM deleted -` - -func (q *Queries) DeleteOldTimelineEvents(ctx context.Context, timeout sqltypes.Duration, type_ EventType) (int64, error) { - row := q.db.QueryRowContext(ctx, deleteOldTimelineEvents, timeout, type_) - var count int64 - err := row.Scan(&count) - return count, err -} - -const dummyQueryTimeline = `-- name: DummyQueryTimeline :one -SELECT id, time_stamp, deployment_id, request_id, type, custom_key_1, custom_key_2, custom_key_3, custom_key_4, payload, parent_request_id FROM timeline WHERE id = $1 -` - -// This is a dummy query to ensure that the Timeline model is generated. -func (q *Queries) DummyQueryTimeline(ctx context.Context, id int64) (Timeline, error) { - row := q.db.QueryRowContext(ctx, dummyQueryTimeline, id) - var i Timeline - err := row.Scan( - &i.ID, - &i.TimeStamp, - &i.DeploymentID, - &i.RequestID, - &i.Type, - &i.CustomKey1, - &i.CustomKey2, - &i.CustomKey3, - &i.CustomKey4, - &i.Payload, - &i.ParentRequestID, - ) - return i, err -} - -const insertTimelineAsyncExecuteEvent = `-- name: InsertTimelineAsyncExecuteEvent :exec -INSERT INTO timeline ( - deployment_id, - request_id, - time_stamp, - type, - custom_key_1, - custom_key_2, - payload -) -VALUES ( - (SELECT id FROM deployments d WHERE d.key = $1::deployment_key LIMIT 1), - (CASE - WHEN $2::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = $2::TEXT) - END), - $3::TIMESTAMPTZ, - 'async_execute', - $4::TEXT, - $5::TEXT, - $6 -) -` - -type InsertTimelineAsyncExecuteEventParams struct { - DeploymentKey model.DeploymentKey - RequestKey optional.Option[string] - TimeStamp time.Time - Module string - Verb string - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelineAsyncExecuteEvent(ctx context.Context, arg InsertTimelineAsyncExecuteEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelineAsyncExecuteEvent, - arg.DeploymentKey, - arg.RequestKey, - arg.TimeStamp, - arg.Module, - arg.Verb, - arg.Payload, - ) - return err -} - -const insertTimelineCallEvent = `-- name: InsertTimelineCallEvent :exec -INSERT INTO timeline ( - deployment_id, - request_id, - parent_request_id, - time_stamp, - type, - custom_key_1, - custom_key_2, - custom_key_3, - custom_key_4, - payload -) -VALUES ( - (SELECT id FROM deployments WHERE deployments.key = $1::deployment_key), - (CASE - WHEN $2::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = $2::TEXT) - END), - (CASE - WHEN $3::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = $3::TEXT) - END), - $4::TIMESTAMPTZ, - 'call', - $5::TEXT, - $6::TEXT, - $7::TEXT, - $8::TEXT, - $9 -) -` - -type InsertTimelineCallEventParams struct { - DeploymentKey model.DeploymentKey - RequestKey optional.Option[string] - ParentRequestKey optional.Option[string] - TimeStamp time.Time - SourceModule optional.Option[string] - SourceVerb optional.Option[string] - DestModule string - DestVerb string - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelineCallEvent(ctx context.Context, arg InsertTimelineCallEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelineCallEvent, - arg.DeploymentKey, - arg.RequestKey, - arg.ParentRequestKey, - arg.TimeStamp, - arg.SourceModule, - arg.SourceVerb, - arg.DestModule, - arg.DestVerb, - arg.Payload, - ) - return err -} - -const insertTimelineCronScheduledEvent = `-- name: InsertTimelineCronScheduledEvent :exec -INSERT INTO timeline ( - deployment_id, - time_stamp, - type, - custom_key_1, - custom_key_2, - payload -) -VALUES ( - (SELECT id FROM deployments d WHERE d.key = $1::deployment_key LIMIT 1), - $2::TIMESTAMPTZ, - 'cron_scheduled', - $3::TEXT, - $4::TEXT, - $5 -) -` - -type InsertTimelineCronScheduledEventParams struct { - DeploymentKey model.DeploymentKey - TimeStamp time.Time - Module string - Verb string - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelineCronScheduledEvent(ctx context.Context, arg InsertTimelineCronScheduledEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelineCronScheduledEvent, - arg.DeploymentKey, - arg.TimeStamp, - arg.Module, - arg.Verb, - arg.Payload, - ) - return err -} - -const insertTimelineIngressEvent = `-- name: InsertTimelineIngressEvent :exec -INSERT INTO timeline ( - deployment_id, - request_id, - time_stamp, - type, - custom_key_1, - custom_key_2, - custom_key_3, - payload -) -VALUES ( - (SELECT id FROM deployments d WHERE d.key = $1::deployment_key LIMIT 1), - ( - CASE - WHEN $2::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = $2::TEXT LIMIT 1) - END - ), - $3::TIMESTAMPTZ, - 'ingress', - $4::TEXT, - $5::TEXT, - $6::TEXT, - $7 -) -` - -type InsertTimelineIngressEventParams struct { - DeploymentKey model.DeploymentKey - RequestKey optional.Option[string] - TimeStamp time.Time - Module string - Verb string - IngressType string - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelineIngressEvent(ctx context.Context, arg InsertTimelineIngressEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelineIngressEvent, - arg.DeploymentKey, - arg.RequestKey, - arg.TimeStamp, - arg.Module, - arg.Verb, - arg.IngressType, - arg.Payload, - ) - return err -} - -const insertTimelineLogEvent = `-- name: InsertTimelineLogEvent :exec -INSERT INTO timeline ( - deployment_id, - request_id, - time_stamp, - custom_key_1, - type, - payload -) -VALUES ( - (SELECT id FROM deployments d WHERE d.key = $1::deployment_key LIMIT 1), - ( - CASE - WHEN $2::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = $2::TEXT LIMIT 1) - END - ), - $3::TIMESTAMPTZ, - $4::INT, - 'log', - $5 -) -` - -type InsertTimelineLogEventParams struct { - DeploymentKey model.DeploymentKey - RequestKey optional.Option[string] - TimeStamp time.Time - Level int32 - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelineLogEvent(ctx context.Context, arg InsertTimelineLogEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelineLogEvent, - arg.DeploymentKey, - arg.RequestKey, - arg.TimeStamp, - arg.Level, - arg.Payload, - ) - return err -} - -const insertTimelinePubsubConsumeEvent = `-- name: InsertTimelinePubsubConsumeEvent :exec -INSERT INTO timeline ( - deployment_id, - request_id, - time_stamp, - type, - custom_key_1, - custom_key_2, - custom_key_3, - payload -) -VALUES ( - (SELECT id FROM deployments d WHERE d.key = $1::deployment_key LIMIT 1), - (CASE - WHEN $2::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = $2::TEXT) - END), - $3::TIMESTAMPTZ, - 'pubsub_consume', - $4::TEXT, - $5::TEXT, - $6::TEXT, - $7 -) -` - -type InsertTimelinePubsubConsumeEventParams struct { - DeploymentKey model.DeploymentKey - RequestKey optional.Option[string] - TimeStamp time.Time - DestModule optional.Option[string] - DestVerb optional.Option[string] - Topic string - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelinePubsubConsumeEvent(ctx context.Context, arg InsertTimelinePubsubConsumeEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelinePubsubConsumeEvent, - arg.DeploymentKey, - arg.RequestKey, - arg.TimeStamp, - arg.DestModule, - arg.DestVerb, - arg.Topic, - arg.Payload, - ) - return err -} - -const insertTimelinePubsubPublishEvent = `-- name: InsertTimelinePubsubPublishEvent :exec -INSERT INTO timeline ( - deployment_id, - request_id, - time_stamp, - type, - custom_key_1, - custom_key_2, - custom_key_3, - payload -) -VALUES ( - (SELECT id FROM deployments d WHERE d.key = $1::deployment_key LIMIT 1), - (CASE - WHEN $2::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = $2::TEXT) - END), - $3::TIMESTAMPTZ, - 'pubsub_publish', - $4::TEXT, - $5::TEXT, - $6::TEXT, - $7 -) -` - -type InsertTimelinePubsubPublishEventParams struct { - DeploymentKey model.DeploymentKey - RequestKey optional.Option[string] - TimeStamp time.Time - SourceModule string - SourceVerb string - Topic string - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelinePubsubPublishEvent(ctx context.Context, arg InsertTimelinePubsubPublishEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelinePubsubPublishEvent, - arg.DeploymentKey, - arg.RequestKey, - arg.TimeStamp, - arg.SourceModule, - arg.SourceVerb, - arg.Topic, - arg.Payload, - ) - return err -} diff --git a/backend/controller/timeline/internal/timeline_test.go b/backend/controller/timeline/internal/timeline_test.go deleted file mode 100644 index b7eee0d379..0000000000 --- a/backend/controller/timeline/internal/timeline_test.go +++ /dev/null @@ -1,420 +0,0 @@ -package internal - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - "reflect" - "testing" - "time" - - "github.com/alecthomas/assert/v2" - "github.com/alecthomas/types/optional" - - "github.com/TBD54566975/ftl/backend/controller/artefacts" - timeline2 "github.com/TBD54566975/ftl/backend/controller/timeline" - - controllerdal "github.com/TBD54566975/ftl/backend/controller/dal" - dalmodel "github.com/TBD54566975/ftl/backend/controller/dal/model" - "github.com/TBD54566975/ftl/backend/controller/encryption" - "github.com/TBD54566975/ftl/backend/controller/pubsub" - "github.com/TBD54566975/ftl/backend/controller/sql/sqltest" - "github.com/TBD54566975/ftl/internal/log" - "github.com/TBD54566975/ftl/internal/model" - "github.com/TBD54566975/ftl/internal/schema" - "github.com/TBD54566975/ftl/internal/sha256" -) - -func TestTimeline(t *testing.T) { - ctx := log.ContextWithNewDefaultLogger(context.Background()) - conn := sqltest.OpenForTesting(ctx, t) - encryption, err := encryption.New(ctx, conn, encryption.NewBuilder()) - assert.NoError(t, err) - - timeline := timeline2.New(ctx, conn, encryption) - pubSub := pubsub.New(ctx, conn, encryption, optional.None[pubsub.AsyncCallListener](), timeline) - - registry := artefacts.NewForTesting() - controllerDAL := controllerdal.New(ctx, conn, encryption, pubSub, registry) - - var testContent = bytes.Repeat([]byte("sometestcontentthatislongerthanthereadbuffer"), 100) - - t.Run("UpsertModule", func(t *testing.T) { - err = controllerDAL.UpsertModule(ctx, "go", "test") - assert.NoError(t, err) - }) - - var testSha sha256.SHA256 - - t.Run("CreateArtefact", func(t *testing.T) { - testSha, err = registry.Upload(ctx, artefacts.Artefact{Content: testContent}) - assert.NoError(t, err) - }) - - module := &schema.Module{Name: "test"} - var deploymentKey model.DeploymentKey - t.Run("CreateDeployment", func(t *testing.T) { - deploymentKey, err = controllerDAL.CreateDeployment(ctx, "go", module, []dalmodel.DeploymentArtefact{{ - Digest: testSha, - Executable: true, - Path: "dir/filename", - }}) - assert.NoError(t, err) - time.Sleep(200 * time.Millisecond) - }) - - t.Run("SetDeploymentReplicas", func(t *testing.T) { - err := controllerDAL.SetDeploymentReplicas(ctx, deploymentKey, 1) - assert.NoError(t, err) - }) - - requestKey := model.NewRequestKey(model.OriginIngress, "GET /test") - t.Run("CreateIngressRequest", func(t *testing.T) { - err = controllerDAL.CreateRequest(ctx, requestKey, "127.0.0.1:1234") - assert.NoError(t, err) - }) - - callEvent := &timeline2.CallEvent{ - Time: time.Now().Round(time.Millisecond), - DeploymentKey: deploymentKey, - RequestKey: optional.Some(requestKey), - Request: []byte("{}"), - Response: []byte(`{"time":"now"}`), - DestVerb: schema.Ref{Module: "time", Name: "time"}, - } - - t.Run("InsertCallEvent", func(t *testing.T) { - call := timeline2.CallEventToCallForTesting(callEvent) - timeline.EnqueueEvent(ctx, call) - time.Sleep(200 * time.Millisecond) - }) - - logEvent := &timeline2.LogEvent{ - Log: timeline2.Log{ - Time: time.Now().Round(time.Millisecond), - DeploymentKey: deploymentKey, - RequestKey: optional.Some(requestKey), - Level: int32(log.Warn), - Attributes: map[string]string{"attr": "value"}, - Message: "A log entry", - }, - } - t.Run("InsertLogEntry", func(t *testing.T) { - timeline.EnqueueEvent(ctx, &logEvent.Log) - time.Sleep(200 * time.Millisecond) - }) - - ingressEvent := &timeline2.IngressEvent{ - DeploymentKey: deploymentKey, - RequestKey: optional.Some(requestKey), - Verb: schema.Ref{Module: "time", Name: "time"}, - Method: "GET", - Path: "/time", - StatusCode: 200, - Time: time.Now().Round(time.Millisecond), - Request: []byte(`{"request":"body"}`), - RequestHeader: json.RawMessage(`{"request":["header"]}`), - Response: []byte(`{"response":"body"}`), - ResponseHeader: json.RawMessage(`{"response":["header"]}`), - } - - t.Run("InsertHTTPIngressEvent", func(t *testing.T) { - timeline.EnqueueEvent(ctx, &timeline2.Ingress{ - DeploymentKey: ingressEvent.DeploymentKey, - RequestKey: ingressEvent.RequestKey.MustGet(), - StartTime: ingressEvent.Time, - Verb: &ingressEvent.Verb, - RequestMethod: ingressEvent.Method, - RequestPath: ingressEvent.Path, - RequestHeaders: http.Header(map[string][]string{"request": {"header"}}), - RequestBody: ingressEvent.Request, - ResponseStatus: ingressEvent.StatusCode, - ResponseHeaders: http.Header(map[string][]string{"response": {"header"}}), - ResponseBody: ingressEvent.Response, - }) - time.Sleep(200 * time.Millisecond) - }) - - cronEvent := &timeline2.CronScheduledEvent{ - CronScheduled: timeline2.CronScheduled{ - DeploymentKey: deploymentKey, - Verb: schema.Ref{Module: "time", Name: "time"}, - Time: time.Now().Round(time.Millisecond), - ScheduledAt: time.Now().Add(time.Minute).Round(time.Millisecond).UTC(), - Schedule: "* * * * *", - Error: optional.None[string](), - }, - } - - t.Run("InsertCronScheduledEvent", func(t *testing.T) { - timeline.EnqueueEvent(ctx, &timeline2.CronScheduled{ - DeploymentKey: cronEvent.DeploymentKey, - Verb: cronEvent.Verb, - Time: cronEvent.Time, - ScheduledAt: cronEvent.ScheduledAt, - Schedule: cronEvent.Schedule, - Error: cronEvent.Error, - }) - time.Sleep(200 * time.Millisecond) - }) - - asyncEvent := &timeline2.AsyncExecuteEvent{ - AsyncExecute: timeline2.AsyncExecute{ - DeploymentKey: deploymentKey, - RequestKey: optional.Some(requestKey.String()), - EventType: timeline2.AsyncExecuteEventTypeCron, - Verb: schema.Ref{Module: "time", Name: "time"}, - Time: time.Now().Round(time.Millisecond), - Error: optional.None[string](), - }, - } - - t.Run("InsertAsyncExecuteEvent", func(t *testing.T) { - timeline.EnqueueEvent(ctx, &timeline2.AsyncExecute{ - DeploymentKey: asyncEvent.DeploymentKey, - RequestKey: asyncEvent.RequestKey, - EventType: asyncEvent.EventType, - Verb: asyncEvent.Verb, - Time: asyncEvent.Time, - Error: asyncEvent.Error, - }) - time.Sleep(200 * time.Millisecond) - }) - - pubSubPublishEvent := &timeline2.PubSubPublishEvent{ - Request: []byte("null"), - PubSubPublish: timeline2.PubSubPublish{ - DeploymentKey: deploymentKey, - RequestKey: optional.Some(requestKey.String()), - Time: time.Now().Round(time.Millisecond), - SourceVerb: schema.Ref{Module: "time", Name: "time"}, - Topic: "test", - Error: optional.None[string](), - }, - } - - t.Run("InsertPubSubPublishEvent", func(t *testing.T) { - timeline.EnqueueEvent(ctx, &timeline2.PubSubPublish{ - DeploymentKey: pubSubPublishEvent.DeploymentKey, - RequestKey: pubSubPublishEvent.RequestKey, - Time: pubSubPublishEvent.Time, - SourceVerb: pubSubPublishEvent.SourceVerb, - Topic: pubSubPublishEvent.Topic, - Error: pubSubPublishEvent.Error, - }) - time.Sleep(200 * time.Millisecond) - }) - - pubSubConsumeEvent := &timeline2.PubSubConsumeEvent{ - PubSubConsume: timeline2.PubSubConsume{ - DeploymentKey: deploymentKey, - RequestKey: optional.Some(requestKey.String()), - Time: time.Now().Round(time.Millisecond), - DestVerb: optional.Some(schema.RefKey{Module: "time", Name: "time"}), - Topic: "test", - Error: optional.None[string](), - }, - } - - t.Run("InsertPubSubConsumeEvent", func(t *testing.T) { - timeline.EnqueueEvent(ctx, &timeline2.PubSubConsume{ - DeploymentKey: pubSubConsumeEvent.DeploymentKey, - RequestKey: pubSubConsumeEvent.RequestKey, - Time: pubSubConsumeEvent.Time, - DestVerb: pubSubConsumeEvent.DestVerb, - Topic: pubSubConsumeEvent.Topic, - Error: pubSubConsumeEvent.Error, - }) - time.Sleep(200 * time.Millisecond) - }) - - expectedDeploymentUpdatedEvent := &timeline2.DeploymentUpdatedEvent{ - DeploymentKey: deploymentKey, - MinReplicas: 1, - } - - t.Run("QueryEvents", func(t *testing.T) { - t.Run("Limit", func(t *testing.T) { - events, err := timeline.QueryTimeline(ctx, 1) - assert.NoError(t, err) - assert.Equal(t, 1, len(events)) - }) - - t.Run("NoFilters", func(t *testing.T) { - events, err := timeline.QueryTimeline(ctx, 1000) - assert.NoError(t, err) - assertEventsEqual(t, []timeline2.Event{expectedDeploymentUpdatedEvent, callEvent, logEvent, ingressEvent, cronEvent, asyncEvent, pubSubPublishEvent, pubSubConsumeEvent}, events) - }) - - t.Run("ByDeployment", func(t *testing.T) { - events, err := timeline.QueryTimeline(ctx, 1000, timeline2.FilterDeployments(deploymentKey)) - assert.NoError(t, err) - assertEventsEqual(t, []timeline2.Event{expectedDeploymentUpdatedEvent, callEvent, logEvent, ingressEvent, cronEvent, asyncEvent, pubSubPublishEvent, pubSubConsumeEvent}, events) - }) - - t.Run("ByCall", func(t *testing.T) { - events, err := timeline.QueryTimeline(ctx, 1000, timeline2.FilterTypes(timeline2.EventTypeCall), timeline2.FilterCall(optional.None[string](), "time", optional.None[string]())) - assert.NoError(t, err) - assertEventsEqual(t, []timeline2.Event{callEvent}, events) - }) - - t.Run("ByModule", func(t *testing.T) { - eventTypes := []timeline2.EventType{ - timeline2.EventTypeCall, - timeline2.EventTypeIngress, - timeline2.EventTypeAsyncExecute, - timeline2.EventTypePubSubPublish, - timeline2.EventTypePubSubConsume, - } - events, err := timeline.QueryTimeline(ctx, 1000, timeline2.FilterTypes(eventTypes...), timeline2.FilterModule("time", optional.None[string]())) - assert.NoError(t, err) - assertEventsEqual(t, []timeline2.Event{callEvent, ingressEvent, asyncEvent, pubSubPublishEvent, pubSubConsumeEvent}, events) - }) - - t.Run("ByModuleWithVerb", func(t *testing.T) { - events, err := timeline.QueryTimeline(ctx, 1000, timeline2.FilterTypes(timeline2.EventTypeIngress), timeline2.FilterModule("time", optional.Some("time"))) - assert.NoError(t, err) - assertEventsEqual(t, []timeline2.Event{ingressEvent}, events) - }) - - t.Run("ByLogLevel", func(t *testing.T) { - events, err := timeline.QueryTimeline(ctx, 1000, timeline2.FilterTypes(timeline2.EventTypeLog), timeline2.FilterLogLevel(log.Trace)) - assert.NoError(t, err) - assertEventsEqual(t, []timeline2.Event{logEvent}, events) - }) - - t.Run("ByRequests", func(t *testing.T) { - events, err := timeline.QueryTimeline(ctx, 1000, timeline2.FilterRequests(requestKey)) - assert.NoError(t, err) - assertEventsEqual(t, []timeline2.Event{callEvent, logEvent, ingressEvent, asyncEvent, pubSubPublishEvent, pubSubConsumeEvent}, events) - }) - }) -} - -func normaliseEvents(events []timeline2.Event) []timeline2.Event { - for i := range events { - event := events[i] - re := reflect.Indirect(reflect.ValueOf(event)) - f := re.FieldByName("Time") - f.Set(reflect.Zero(f.Type())) - f = re.FieldByName("ID") - f.Set(reflect.Zero(f.Type())) - events[i] = event - } - - return events -} - -func assertEventsEqual(t *testing.T, expected, actual []timeline2.Event) { - t.Helper() - assert.Equal(t, normaliseEvents(expected), normaliseEvents(actual), assert.Exclude[time.Duration](), assert.Exclude[time.Time]()) -} - -func TestDeleteOldEvents(t *testing.T) { - ctx := log.ContextWithNewDefaultLogger(context.Background()) - conn := sqltest.OpenForTesting(ctx, t) - encryption, err := encryption.New(ctx, conn, encryption.NewBuilder()) - assert.NoError(t, err) - - timeline := timeline2.New(ctx, conn, encryption) - registry := artefacts.NewForTesting() - pubSub := pubsub.New(ctx, conn, encryption, optional.None[pubsub.AsyncCallListener](), timeline) - controllerDAL := controllerdal.New(ctx, conn, encryption, pubSub, registry) - - var testContent = bytes.Repeat([]byte("sometestcontentthatislongerthanthereadbuffer"), 100) - var testSha sha256.SHA256 - - t.Run("CreateArtefact", func(t *testing.T) { - testSha, err = registry.Upload(ctx, artefacts.Artefact{Content: testContent}) - assert.NoError(t, err) - }) - - module := &schema.Module{Name: "test"} - var deploymentKey model.DeploymentKey - t.Run("CreateDeployment", func(t *testing.T) { - deploymentKey, err = controllerDAL.CreateDeployment(ctx, "go", module, []dalmodel.DeploymentArtefact{{ - Digest: testSha, - Executable: true, - Path: "dir/filename", - }}) - assert.NoError(t, err) - }) - - requestKey := model.NewRequestKey(model.OriginIngress, "GET /test") - // week old event - callEvent := &timeline2.CallEvent{ - Time: time.Now().Add(-24 * 7 * time.Hour).Round(time.Millisecond), - DeploymentKey: deploymentKey, - RequestKey: optional.Some(requestKey), - Request: []byte("{}"), - Response: []byte(`{"time": "now"}`), - DestVerb: schema.Ref{Module: "time", Name: "time"}, - } - - t.Run("InsertCallEvent", func(t *testing.T) { - call := timeline2.CallEventToCallForTesting(callEvent) - timeline.EnqueueEvent(ctx, call) - time.Sleep(200 * time.Millisecond) - }) - // hour old event - callEvent = &timeline2.CallEvent{ - Time: time.Now().Add(-1 * time.Hour).Round(time.Millisecond), - DeploymentKey: deploymentKey, - RequestKey: optional.Some(requestKey), - Request: []byte("{}"), - Response: []byte(`{"time": "now"}`), - DestVerb: schema.Ref{Module: "time", Name: "time"}, - } - t.Run("InsertCallEvent", func(t *testing.T) { - call := timeline2.CallEventToCallForTesting(callEvent) - timeline.EnqueueEvent(ctx, call) - time.Sleep(200 * time.Millisecond) - }) - - // week old event - logEvent := &timeline2.LogEvent{ - Log: timeline2.Log{ - Time: time.Now().Add(-24 * 7 * time.Hour).Round(time.Millisecond), - DeploymentKey: deploymentKey, - RequestKey: optional.Some(requestKey), - Level: int32(log.Warn), - Attributes: map[string]string{"attr": "value"}, - Message: "A log entry", - }, - } - t.Run("InsertLogEntry", func(t *testing.T) { - timeline.EnqueueEvent(ctx, &logEvent.Log) - time.Sleep(200 * time.Millisecond) - }) - - // hour old event - logEvent = &timeline2.LogEvent{ - Log: timeline2.Log{ - Time: time.Now().Add(-1 * time.Hour).Round(time.Millisecond), - DeploymentKey: deploymentKey, - RequestKey: optional.Some(requestKey), - Level: int32(log.Warn), - Attributes: map[string]string{"attr": "value"}, - Message: "A log entry", - }, - } - t.Run("InsertLogEntry", func(t *testing.T) { - timeline.EnqueueEvent(ctx, &logEvent.Log) - time.Sleep(200 * time.Millisecond) - }) - - t.Run("DeleteOldEvents", func(t *testing.T) { - count, err := timeline.DeleteOldEvents(ctx, timeline2.EventTypeCall, 2*24*time.Hour) - assert.NoError(t, err) - assert.Equal(t, int64(1), count) - - count, err = timeline.DeleteOldEvents(ctx, timeline2.EventTypeLog, time.Minute) - assert.NoError(t, err) - assert.Equal(t, int64(2), count) - - count, err = timeline.DeleteOldEvents(ctx, timeline2.EventTypeLog, time.Minute) - assert.NoError(t, err) - assert.Equal(t, int64(0), count) - }) -} diff --git a/backend/controller/timeline/query.go b/backend/controller/timeline/query.go deleted file mode 100644 index d24a8afef1..0000000000 --- a/backend/controller/timeline/query.go +++ /dev/null @@ -1,490 +0,0 @@ -package timeline - -import ( - "context" - stdsql "database/sql" - "fmt" - "strconv" - "time" - - "github.com/alecthomas/types/optional" - - "github.com/TBD54566975/ftl/backend/controller/timeline/internal/sql" - "github.com/TBD54566975/ftl/backend/libdal" - "github.com/TBD54566975/ftl/internal/log" - "github.com/TBD54566975/ftl/internal/model" - "github.com/TBD54566975/ftl/internal/schema" -) - -type eventFilterCall struct { - sourceModule optional.Option[string] - destModule string - destVerb optional.Option[string] -} - -type eventFilterModule struct { - module string - verb optional.Option[string] -} - -type eventFilter struct { - level *log.Level - calls []*eventFilterCall - module []*eventFilterModule - types []EventType - deployments []model.DeploymentKey - requests []string - newerThan time.Time - olderThan time.Time - idHigherThan int64 - idLowerThan int64 - descending bool -} - -type eventRow struct { - sql.Timeline - DeploymentKey model.DeploymentKey - RequestKey optional.Option[model.RequestKey] -} - -type TimelineFilter func(query *eventFilter) - -func FilterLogLevel(level log.Level) TimelineFilter { - return func(query *eventFilter) { - query.level = &level - } -} - -// FilterCall filters call events between the given modules. -// -// May be called multiple times. -func FilterCall(sourceModule optional.Option[string], destModule string, destVerb optional.Option[string]) TimelineFilter { - return func(query *eventFilter) { - query.calls = append(query.calls, &eventFilterCall{sourceModule: sourceModule, destModule: destModule, destVerb: destVerb}) - } -} - -func FilterModule(module string, verb optional.Option[string]) TimelineFilter { - return func(query *eventFilter) { - query.module = append(query.module, &eventFilterModule{module: module, verb: verb}) - } -} - -func FilterDeployments(deploymentKeys ...model.DeploymentKey) TimelineFilter { - return func(query *eventFilter) { - query.deployments = append(query.deployments, deploymentKeys...) - } -} - -func FilterRequests(requestKeys ...model.RequestKey) TimelineFilter { - return func(query *eventFilter) { - for _, request := range requestKeys { - query.requests = append(query.requests, request.String()) - } - } -} - -func FilterTypes(types ...sql.EventType) TimelineFilter { - return func(query *eventFilter) { - query.types = append(query.types, types...) - } -} - -// FilterTimeRange filters events between the given times, inclusive. -// -// Either maybe be zero to indicate no upper or lower bound. -func FilterTimeRange(olderThan, newerThan time.Time) TimelineFilter { - return func(query *eventFilter) { - query.newerThan = newerThan - query.olderThan = olderThan - } -} - -// FilterIDRange filters events between the given IDs, inclusive. -func FilterIDRange(higherThan, lowerThan int64) TimelineFilter { - return func(query *eventFilter) { - query.idHigherThan = higherThan - query.idLowerThan = lowerThan - } -} - -// FilterDescending returns events in descending order. -func FilterDescending() TimelineFilter { - return func(query *eventFilter) { - query.descending = true - } -} - -func (s *Service) QueryTimeline(ctx context.Context, limit int, filters ...TimelineFilter) ([]Event, error) { - if limit < 1 { - return nil, fmt.Errorf("limit must be >= 1, got %d", limit) - } - - // Build query. - q := `SELECT e.id AS id, - e.deployment_id, - ir.key AS request_key, - e.time_stamp, - e.custom_key_1, - e.custom_key_2, - e.custom_key_3, - e.custom_key_4, - e.type, - e.payload - FROM timeline e - LEFT JOIN requests ir on e.request_id = ir.id - WHERE true -- The "true" is to simplify the ANDs below. - ` - - filter := eventFilter{} - for _, f := range filters { - f(&filter) - } - var args []any - index := 1 - param := func(v any) int { - args = append(args, v) - index++ - return index - 1 - } - if !filter.olderThan.IsZero() { - q += fmt.Sprintf(" AND e.time_stamp <= $%d::TIMESTAMPTZ", param(filter.olderThan)) - } - if !filter.newerThan.IsZero() { - q += fmt.Sprintf(" AND e.time_stamp >= $%d::TIMESTAMPTZ", param(filter.newerThan)) - } - if filter.idHigherThan != 0 { - q += fmt.Sprintf(" AND e.id >= $%d::BIGINT", param(filter.idHigherThan)) - } - if filter.idLowerThan != 0 { - q += fmt.Sprintf(" AND e.id <= $%d::BIGINT", param(filter.idLowerThan)) - } - deploymentKeys := map[int64]model.DeploymentKey{} - // TODO: We should probably make it mandatory to provide at least one deployment. - deploymentQuery := `SELECT id, key FROM deployments` - deploymentArgs := []any{} - if len(filter.deployments) != 0 { - // Unfortunately, if we use a join here, PG will do a sequential scan on - // timeline and deployments, making a 7ms query into a 700ms query. - // https://www.pgexplain.dev/plan/ecd44488-6060-4ad1-a9b4-49d092c3de81 - deploymentQuery += ` WHERE key = ANY($1::TEXT[])` - deploymentArgs = append(deploymentArgs, filter.deployments) - } - rows, err := s.conn.QueryContext(ctx, deploymentQuery, deploymentArgs...) - if err != nil { - return nil, libdal.TranslatePGError(err) - } - defer rows.Close() // nolint:errcheck - deploymentIDs := []int64{} - for rows.Next() { - var id int64 - var key model.DeploymentKey - if err := rows.Scan(&id, &key); err != nil { - return nil, err - } - - deploymentIDs = append(deploymentIDs, id) - deploymentKeys[id] = key - } - - q += fmt.Sprintf(` AND e.deployment_id = ANY($%d::BIGINT[])`, param(deploymentIDs)) - - if filter.requests != nil { - q += fmt.Sprintf(` AND ir.key = ANY($%d::TEXT[])`, param(filter.requests)) - } - if filter.types != nil { - // Why are we double casting? Because I hit "cannot find encode plan" and - // this works around it: https://github.com/jackc/pgx/issues/338#issuecomment-333399112 - q += fmt.Sprintf(` AND e.type = ANY($%d::text[]::event_type[])`, param(filter.types)) - } - if filter.level != nil { - q += fmt.Sprintf(" AND (e.type != 'log' OR (e.type = 'log' AND e.custom_key_1::INT >= $%d::INT))\n", param(*filter.level)) - } - if len(filter.calls) > 0 { - q += " AND (" - for i, call := range filter.calls { - if i > 0 { - q += " OR " - } - if sourceModule, ok := call.sourceModule.Get(); ok { - q += fmt.Sprintf("(e.type != 'call' OR (e.type = 'call' AND e.custom_key_1 = $%d AND e.custom_key_3 = $%d))", param(sourceModule), param(call.destModule)) - } else if destVerb, ok := call.destVerb.Get(); ok { - q += fmt.Sprintf("(e.type != 'call' OR (e.type = 'call' AND e.custom_key_3 = $%d AND e.custom_key_4 = $%d))", param(call.destModule), param(destVerb)) - } else { - q += fmt.Sprintf("(e.type != 'call' OR (e.type = 'call' AND e.custom_key_3 = $%d))", param(call.destModule)) - } - } - q += ")\n" - } - - if len(filter.module) > 0 { - q += " AND (" - for i, module := range filter.module { - if i > 0 { - q += " OR " - } - if verb, ok := module.verb.Get(); ok { - q += fmt.Sprintf( - "((e.type = 'call' AND e.custom_key_3 = $%d AND e.custom_key_4 = $%d) OR "+ - "(e.type = 'ingress' AND e.custom_key_1 = $%d AND e.custom_key_2 = $%d) OR "+ - "(e.type = 'async_execute' AND e.custom_key_1 = $%d AND e.custom_key_2 = $%d) OR "+ - "(e.type = 'pubsub_publish' AND e.custom_key_1 = $%d AND e.custom_key_2 = $%d) OR "+ - "(e.type = 'pubsub_consume' AND e.custom_key_1 = $%d AND e.custom_key_2 = $%d))", - param(module.module), param(verb), - param(module.module), param(verb), - param(module.module), param(verb), - param(module.module), param(verb), - param(module.module), param(verb), - ) - } else { - q += fmt.Sprintf( - "((e.type = 'call' AND e.custom_key_3 = $%d) OR "+ - "(e.type = 'ingress' AND e.custom_key_1 = $%d) OR "+ - "(e.type = 'async_execute' AND e.custom_key_1 = $%d) OR "+ - "(e.type = 'pubsub_publish' AND e.custom_key_1 = $%d) OR "+ - "(e.type = 'pubsub_consume' AND e.custom_key_1 = $%d))", - param(module.module), param(module.module), param(module.module), - param(module.module), param(module.module), - ) - } - } - q += ")\n" - } - - if filter.descending { - q += " ORDER BY e.time_stamp DESC, e.id DESC" - } else { - q += " ORDER BY e.time_stamp ASC, e.id ASC" - } - - q += fmt.Sprintf(" LIMIT %d", limit) - - // Issue query. - rows, err = s.conn.QueryContext(ctx, q, args...) - if err != nil { - return nil, fmt.Errorf("%s: %w", q, libdal.TranslatePGError(err)) - } - defer rows.Close() - - events, err := s.transformRowsToTimelineEvents(deploymentKeys, rows) - if err != nil { - return nil, err - } - return events, nil -} - -func (s *Service) transformRowsToTimelineEvents(deploymentKeys map[int64]model.DeploymentKey, rows *stdsql.Rows) ([]Event, error) { - var out []Event - for rows.Next() { - row := eventRow{} - var deploymentID int64 - err := rows.Scan( - &row.ID, &deploymentID, &row.RequestKey, &row.TimeStamp, - &row.CustomKey1, &row.CustomKey2, &row.CustomKey3, &row.CustomKey4, - &row.Type, &row.Payload, - ) - if err != nil { - return nil, err - } - - row.DeploymentKey = deploymentKeys[deploymentID] - - switch row.Type { - case sql.EventTypeLog: - var jsonPayload eventLogJSON - if err := s.encryption.DecryptJSON(&row.Payload, &jsonPayload); err != nil { - return nil, fmt.Errorf("failed to decrypt log event: %w", err) - } - - level, err := strconv.ParseInt(row.CustomKey1.MustGet(), 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid log level: %q: %w", row.CustomKey1.MustGet(), err) - } - out = append(out, &LogEvent{ - ID: row.ID, - Log: Log{ - DeploymentKey: row.DeploymentKey, - RequestKey: row.RequestKey, - Time: row.TimeStamp, - Level: int32(level), - Attributes: jsonPayload.Attributes, - Message: jsonPayload.Message, - Error: jsonPayload.Error, - }, - }) - - case sql.EventTypeCall: - var jsonPayload eventCallJSON - if err := s.encryption.DecryptJSON(&row.Payload, &jsonPayload); err != nil { - return nil, fmt.Errorf("failed to decrypt call event: %w", err) - } - var sourceVerb optional.Option[schema.Ref] - sourceModule, smok := row.CustomKey1.Get() - sourceName, snok := row.CustomKey2.Get() - if smok && snok { - sourceVerb = optional.Some(schema.Ref{Module: sourceModule, Name: sourceName}) - } - out = append(out, &CallEvent{ - ID: row.ID, - DeploymentKey: row.DeploymentKey, - RequestKey: row.RequestKey, - Time: row.TimeStamp, - SourceVerb: sourceVerb, - DestVerb: schema.Ref{Module: row.CustomKey3.MustGet(), Name: row.CustomKey4.MustGet()}, - Duration: time.Duration(jsonPayload.DurationMS) * time.Millisecond, - Request: jsonPayload.Request, - Response: jsonPayload.Response, - Error: jsonPayload.Error, - Stack: jsonPayload.Stack, - }) - - case sql.EventTypeDeploymentCreated: - var jsonPayload eventDeploymentCreatedJSON - if err := s.encryption.DecryptJSON(&row.Payload, &jsonPayload); err != nil { - return nil, fmt.Errorf("failed to decrypt deployment created event: %w", err) - } - out = append(out, &DeploymentCreatedEvent{ - ID: row.ID, - DeploymentKey: row.DeploymentKey, - Time: row.TimeStamp, - Language: row.CustomKey1.MustGet(), - ModuleName: row.CustomKey2.MustGet(), - MinReplicas: jsonPayload.MinReplicas, - ReplacedDeployment: jsonPayload.ReplacedDeployment, - }) - - case sql.EventTypeDeploymentUpdated: - var jsonPayload eventDeploymentUpdatedJSON - if err := s.encryption.DecryptJSON(&row.Payload, &jsonPayload); err != nil { - return nil, fmt.Errorf("failed to decrypt deployment updated event: %w", err) - } - out = append(out, &DeploymentUpdatedEvent{ - ID: row.ID, - DeploymentKey: row.DeploymentKey, - Time: row.TimeStamp, - MinReplicas: jsonPayload.MinReplicas, - PrevMinReplicas: jsonPayload.PrevMinReplicas, - }) - - case sql.EventTypeIngress: - var jsonPayload eventIngressJSON - if err := s.encryption.DecryptJSON(&row.Payload, &jsonPayload); err != nil { - return nil, fmt.Errorf("failed to decrypt ingress event: %w", err) - } - out = append(out, &IngressEvent{ - ID: row.ID, - DeploymentKey: row.DeploymentKey, - RequestKey: row.RequestKey, - Verb: schema.Ref{Module: row.CustomKey1.MustGet(), Name: row.CustomKey2.MustGet()}, - Method: jsonPayload.Method, - Path: jsonPayload.Path, - StatusCode: jsonPayload.StatusCode, - Time: row.TimeStamp, - Duration: time.Duration(jsonPayload.DurationMS) * time.Millisecond, - Request: jsonPayload.Request, - RequestHeader: jsonPayload.RequestHeader, - Response: jsonPayload.Response, - ResponseHeader: jsonPayload.ResponseHeader, - Error: jsonPayload.Error, - }) - - case sql.EventTypeCronScheduled: - var jsonPayload eventCronScheduledJSON - if err := s.encryption.DecryptJSON(&row.Payload, &jsonPayload); err != nil { - return nil, fmt.Errorf("failed to decrypt cron scheduled event: %w", err) - } - out = append(out, &CronScheduledEvent{ - ID: row.ID, - Duration: time.Duration(jsonPayload.DurationMS) * time.Millisecond, - CronScheduled: CronScheduled{ - DeploymentKey: row.DeploymentKey, - Verb: schema.Ref{Module: row.CustomKey1.MustGet(), Name: row.CustomKey2.MustGet()}, - Time: row.TimeStamp, - ScheduledAt: jsonPayload.ScheduledAt, - Schedule: jsonPayload.Schedule, - Error: jsonPayload.Error, - }, - }) - - case sql.EventTypeAsyncExecute: - var jsonPayload eventAsyncExecuteJSON - if err := s.encryption.DecryptJSON(&row.Payload, &jsonPayload); err != nil { - return nil, fmt.Errorf("failed to decrypt async execute event: %w", err) - } - requestKey := optional.None[string]() - if rk, ok := row.RequestKey.Get(); ok { - requestKey = optional.Some(rk.String()) - } - out = append(out, &AsyncExecuteEvent{ - ID: row.ID, - Duration: time.Duration(jsonPayload.DurationMS) * time.Millisecond, - AsyncExecute: AsyncExecute{ - DeploymentKey: row.DeploymentKey, - RequestKey: requestKey, - EventType: jsonPayload.EventType, - Verb: schema.Ref{Module: row.CustomKey1.MustGet(), Name: row.CustomKey2.MustGet()}, - Time: row.TimeStamp, - Error: jsonPayload.Error, - }, - }) - - case sql.EventTypePubsubPublish: - var jsonPayload eventPubSubPublishJSON - if err := s.encryption.DecryptJSON(&row.Payload, &jsonPayload); err != nil { - return nil, fmt.Errorf("failed to decrypt pubsub publish event: %w", err) - } - requestKey := optional.None[string]() - if rk, ok := row.RequestKey.Get(); ok { - requestKey = optional.Some(rk.String()) - } - out = append(out, &PubSubPublishEvent{ - ID: row.ID, - Duration: time.Duration(jsonPayload.DurationMS) * time.Millisecond, - Request: jsonPayload.Request, - PubSubPublish: PubSubPublish{ - DeploymentKey: row.DeploymentKey, - RequestKey: requestKey, - Time: row.TimeStamp, - SourceVerb: schema.Ref{Module: row.CustomKey1.MustGet(), Name: row.CustomKey2.MustGet()}, - Topic: jsonPayload.Topic, - Error: jsonPayload.Error, - }, - }) - - case sql.EventTypePubsubConsume: - var jsonPayload eventPubSubConsumeJSON - if err := s.encryption.DecryptJSON(&row.Payload, &jsonPayload); err != nil { - return nil, fmt.Errorf("failed to decrypt pubsub consume event: %w", err) - } - requestKey := optional.None[string]() - if rk, ok := row.RequestKey.Get(); ok { - requestKey = optional.Some(rk.String()) - } - destVerb := optional.None[schema.RefKey]() - if dv, ok := row.CustomKey2.Get(); ok { - dvr := schema.RefKey{Name: dv} - dm, ok := row.CustomKey1.Get() - if ok { - dvr.Module = dm - } - destVerb = optional.Some(dvr) - } - out = append(out, &PubSubConsumeEvent{ - ID: row.ID, - Duration: time.Duration(jsonPayload.DurationMS) * time.Millisecond, - PubSubConsume: PubSubConsume{ - DeploymentKey: row.DeploymentKey, - RequestKey: requestKey, - Time: row.TimeStamp, - DestVerb: destVerb, - Topic: jsonPayload.Topic, - Error: jsonPayload.Error, - }, - }) - - default: - panic("unknown event type: " + row.Type) - } - } - return out, nil -} diff --git a/backend/controller/timeline/timeline.go b/backend/controller/timeline/timeline.go deleted file mode 100644 index 4220c04837..0000000000 --- a/backend/controller/timeline/timeline.go +++ /dev/null @@ -1,169 +0,0 @@ -package timeline - -import ( - "context" - stdsql "database/sql" - "fmt" - "time" - - "github.com/alecthomas/atomic" - - "github.com/TBD54566975/ftl/backend/controller/encryption" - "github.com/TBD54566975/ftl/backend/controller/observability" - "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" - "github.com/TBD54566975/ftl/backend/controller/timeline/internal/sql" - "github.com/TBD54566975/ftl/backend/libdal" - "github.com/TBD54566975/ftl/internal/log" -) - -type EventType = sql.EventType - -// Supported event types. -const ( - EventTypeLog = sql.EventTypeLog - EventTypeCall = sql.EventTypeCall - EventTypeDeploymentCreated = sql.EventTypeDeploymentCreated - EventTypeDeploymentUpdated = sql.EventTypeDeploymentUpdated - EventTypeIngress = sql.EventTypeIngress - EventTypeCronScheduled = sql.EventTypeCronScheduled - EventTypeAsyncExecute = sql.EventTypeAsyncExecute - EventTypePubSubPublish = sql.EventTypePubsubPublish - EventTypePubSubConsume = sql.EventTypePubsubConsume - - maxBatchSize = 16 - maxBatchDelay = 100 * time.Millisecond -) - -// Event types. -// -//sumtype:decl -type Event interface { - GetID() int64 - event() -} - -// InEvent is a marker interface for events that are inserted into the timeline. -type InEvent interface { - toEvent() (Event, error) -} - -type Service struct { - ctx context.Context - conn *stdsql.DB - encryption *encryption.Service - events chan Event - lastDroppedError atomic.Value[time.Time] - lastFailedError atomic.Value[time.Time] -} - -func New(ctx context.Context, conn *stdsql.DB, encryption *encryption.Service) *Service { - var s *Service - events := make(chan Event, 1000) - s = &Service{ - ctx: ctx, - conn: conn, - encryption: encryption, - events: events, - } - go s.processEvents() - return s -} - -func (s *Service) DeleteOldEvents(ctx context.Context, eventType EventType, age time.Duration) (int64, error) { - count, err := sql.New(s.conn).DeleteOldTimelineEvents(ctx, sqltypes.Duration(age), eventType) - return count, libdal.TranslatePGError(err) -} - -// EnqueueEvent asynchronously enqueues an event for insertion into the timeline. -func (s *Service) EnqueueEvent(ctx context.Context, inEvent InEvent) { - event, err := inEvent.toEvent() - if err != nil { - log.FromContext(ctx).Warnf("Failed to convert event to event: %v", err) - return - } - select { - case s.events <- event: - default: - if time.Since(s.lastDroppedError.Load()) > 10*time.Second { - log.FromContext(ctx).Warnf("Dropping event %T due to full queue", event) - s.lastDroppedError.Store(time.Now()) - } - } -} - -func (s *Service) processEvents() { - lastFlush := time.Now() - buffer := make([]Event, 0, maxBatchSize) - for { - select { - case event := <-s.events: - buffer = append(buffer, event) - - if len(buffer) < maxBatchSize || time.Since(lastFlush) < maxBatchDelay { - continue - } - s.flushEvents(buffer) - buffer = nil - - case <-time.After(maxBatchDelay): - if len(buffer) == 0 { - continue - } - s.flushEvents(buffer) - buffer = nil - } - } -} - -// Flush all events in the buffer to the database in a single transaction. -func (s *Service) flushEvents(events []Event) { - logger := log.FromContext(s.ctx).Scope("timeline") - tx, err := s.conn.Begin() - if err != nil { - logger.Errorf(err, "Failed to start transaction") - return - } - querier := sql.New(tx) - var lastError error - failures := 0 - for _, event := range events { - var err error - switch e := event.(type) { - case *CallEvent: - err = s.insertCallEvent(s.ctx, querier, e) - case *LogEvent: - err = s.insertLogEvent(s.ctx, querier, e) - case *IngressEvent: - err = s.insertHTTPIngress(s.ctx, querier, e) - case *CronScheduledEvent: - err = s.insertCronScheduledEvent(s.ctx, querier, e) - case *AsyncExecuteEvent: - err = s.insertAsyncExecuteEvent(s.ctx, querier, e) - case *PubSubPublishEvent: - err = s.insertPubSubPublishEvent(s.ctx, querier, e) - case *PubSubConsumeEvent: - err = s.insertPubSubConsumeEvent(s.ctx, querier, e) - case *DeploymentCreatedEvent, *DeploymentUpdatedEvent: - // TODO: Implement - default: - panic(fmt.Sprintf("unexpected event type: %T", e)) - } - if err != nil { - lastError = err - failures++ - } - } - err = tx.Commit() - if err != nil { - failures = len(events) - lastError = err - } - if lastError != nil { - if time.Since(s.lastFailedError.Load()) > 10*time.Second { - logger.Errorf(lastError, "Failed to insert %d events, most recent error", failures) - s.lastFailedError.Store(time.Now()) - } - observability.Timeline.Failed(s.ctx, failures) - } - observability.Timeline.Inserted(s.ctx, len(events)-failures) -} diff --git a/backend/ingress/handler.go b/backend/ingress/handler.go index 06d9b3cea9..be87e3bad0 100644 --- a/backend/ingress/handler.go +++ b/backend/ingress/handler.go @@ -9,10 +9,10 @@ import ( "time" "connectrpc.com/connect" + "github.com/TBD54566975/ftl/backend/timeline" "github.com/alecthomas/types/optional" "github.com/TBD54566975/ftl/backend/controller/observability" - "github.com/TBD54566975/ftl/backend/controller/timeline" "github.com/TBD54566975/ftl/backend/libdal" schemapb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/schema/v1" ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" diff --git a/backend/protos/xyz/block/ftl/timeline/v1/event.pb.go b/backend/protos/xyz/block/ftl/timeline/v1/event.pb.go index f171432a6c..99a4e3d55d 100644 --- a/backend/protos/xyz/block/ftl/timeline/v1/event.pb.go +++ b/backend/protos/xyz/block/ftl/timeline/v1/event.pb.go @@ -207,7 +207,7 @@ type LogEvent struct { DeploymentKey string `protobuf:"bytes,1,opt,name=deployment_key,json=deploymentKey,proto3" json:"deployment_key,omitempty"` RequestKey *string `protobuf:"bytes,2,opt,name=request_key,json=requestKey,proto3,oneof" json:"request_key,omitempty"` - TimeStamp *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=time_stamp,json=timeStamp,proto3" json:"time_stamp,omitempty"` + Timestamp *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` LogLevel int32 `protobuf:"varint,4,opt,name=log_level,json=logLevel,proto3" json:"log_level,omitempty"` Attributes map[string]string `protobuf:"bytes,5,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Message string `protobuf:"bytes,6,opt,name=message,proto3" json:"message,omitempty"` @@ -259,9 +259,9 @@ func (x *LogEvent) GetRequestKey() string { return "" } -func (x *LogEvent) GetTimeStamp() *timestamppb.Timestamp { +func (x *LogEvent) GetTimestamp() *timestamppb.Timestamp { if x != nil { - return x.TimeStamp + return x.Timestamp } return nil } @@ -308,7 +308,7 @@ type CallEvent struct { RequestKey *string `protobuf:"bytes,1,opt,name=request_key,json=requestKey,proto3,oneof" json:"request_key,omitempty"` DeploymentKey string `protobuf:"bytes,2,opt,name=deployment_key,json=deploymentKey,proto3" json:"deployment_key,omitempty"` - TimeStamp *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=time_stamp,json=timeStamp,proto3" json:"time_stamp,omitempty"` + Timestamp *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` SourceVerbRef *v1.Ref `protobuf:"bytes,11,opt,name=source_verb_ref,json=sourceVerbRef,proto3,oneof" json:"source_verb_ref,omitempty"` DestinationVerbRef *v1.Ref `protobuf:"bytes,12,opt,name=destination_verb_ref,json=destinationVerbRef,proto3" json:"destination_verb_ref,omitempty"` Duration *durationpb.Duration `protobuf:"bytes,6,opt,name=duration,proto3" json:"duration,omitempty"` @@ -362,9 +362,9 @@ func (x *CallEvent) GetDeploymentKey() string { return "" } -func (x *CallEvent) GetTimeStamp() *timestamppb.Timestamp { +func (x *CallEvent) GetTimestamp() *timestamppb.Timestamp { if x != nil { - return x.TimeStamp + return x.Timestamp } return nil } @@ -567,7 +567,7 @@ type IngressEvent struct { Method string `protobuf:"bytes,4,opt,name=method,proto3" json:"method,omitempty"` Path string `protobuf:"bytes,5,opt,name=path,proto3" json:"path,omitempty"` StatusCode int32 `protobuf:"varint,7,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"` - TimeStamp *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=time_stamp,json=timeStamp,proto3" json:"time_stamp,omitempty"` + Timestamp *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Duration *durationpb.Duration `protobuf:"bytes,9,opt,name=duration,proto3" json:"duration,omitempty"` Request string `protobuf:"bytes,10,opt,name=request,proto3" json:"request,omitempty"` RequestHeader string `protobuf:"bytes,11,opt,name=request_header,json=requestHeader,proto3" json:"request_header,omitempty"` @@ -648,9 +648,9 @@ func (x *IngressEvent) GetStatusCode() int32 { return 0 } -func (x *IngressEvent) GetTimeStamp() *timestamppb.Timestamp { +func (x *IngressEvent) GetTimestamp() *timestamppb.Timestamp { if x != nil { - return x.TimeStamp + return x.Timestamp } return nil } @@ -704,7 +704,7 @@ type CronScheduledEvent struct { DeploymentKey string `protobuf:"bytes,1,opt,name=deployment_key,json=deploymentKey,proto3" json:"deployment_key,omitempty"` VerbRef *v1.Ref `protobuf:"bytes,2,opt,name=verb_ref,json=verbRef,proto3" json:"verb_ref,omitempty"` - TimeStamp *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=time_stamp,json=timeStamp,proto3" json:"time_stamp,omitempty"` + Timestamp *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Duration *durationpb.Duration `protobuf:"bytes,4,opt,name=duration,proto3" json:"duration,omitempty"` ScheduledAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=scheduled_at,json=scheduledAt,proto3" json:"scheduled_at,omitempty"` Schedule string `protobuf:"bytes,6,opt,name=schedule,proto3" json:"schedule,omitempty"` @@ -755,9 +755,9 @@ func (x *CronScheduledEvent) GetVerbRef() *v1.Ref { return nil } -func (x *CronScheduledEvent) GetTimeStamp() *timestamppb.Timestamp { +func (x *CronScheduledEvent) GetTimestamp() *timestamppb.Timestamp { if x != nil { - return x.TimeStamp + return x.Timestamp } return nil } @@ -798,7 +798,7 @@ type AsyncExecuteEvent struct { DeploymentKey string `protobuf:"bytes,1,opt,name=deployment_key,json=deploymentKey,proto3" json:"deployment_key,omitempty"` RequestKey *string `protobuf:"bytes,2,opt,name=request_key,json=requestKey,proto3,oneof" json:"request_key,omitempty"` VerbRef *v1.Ref `protobuf:"bytes,3,opt,name=verb_ref,json=verbRef,proto3" json:"verb_ref,omitempty"` - TimeStamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=time_stamp,json=timeStamp,proto3" json:"time_stamp,omitempty"` + Timestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Duration *durationpb.Duration `protobuf:"bytes,5,opt,name=duration,proto3" json:"duration,omitempty"` AsyncEventType AsyncExecuteEventType `protobuf:"varint,6,opt,name=async_event_type,json=asyncEventType,proto3,enum=xyz.block.ftl.timeline.v1.AsyncExecuteEventType" json:"async_event_type,omitempty"` Error *string `protobuf:"bytes,7,opt,name=error,proto3,oneof" json:"error,omitempty"` @@ -855,9 +855,9 @@ func (x *AsyncExecuteEvent) GetVerbRef() *v1.Ref { return nil } -func (x *AsyncExecuteEvent) GetTimeStamp() *timestamppb.Timestamp { +func (x *AsyncExecuteEvent) GetTimestamp() *timestamppb.Timestamp { if x != nil { - return x.TimeStamp + return x.Timestamp } return nil } @@ -891,7 +891,7 @@ type PubSubPublishEvent struct { DeploymentKey string `protobuf:"bytes,1,opt,name=deployment_key,json=deploymentKey,proto3" json:"deployment_key,omitempty"` RequestKey *string `protobuf:"bytes,2,opt,name=request_key,json=requestKey,proto3,oneof" json:"request_key,omitempty"` VerbRef *v1.Ref `protobuf:"bytes,3,opt,name=verb_ref,json=verbRef,proto3" json:"verb_ref,omitempty"` - TimeStamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=time_stamp,json=timeStamp,proto3" json:"time_stamp,omitempty"` + Timestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Duration *durationpb.Duration `protobuf:"bytes,5,opt,name=duration,proto3" json:"duration,omitempty"` Topic string `protobuf:"bytes,6,opt,name=topic,proto3" json:"topic,omitempty"` Request string `protobuf:"bytes,7,opt,name=request,proto3" json:"request,omitempty"` @@ -949,9 +949,9 @@ func (x *PubSubPublishEvent) GetVerbRef() *v1.Ref { return nil } -func (x *PubSubPublishEvent) GetTimeStamp() *timestamppb.Timestamp { +func (x *PubSubPublishEvent) GetTimestamp() *timestamppb.Timestamp { if x != nil { - return x.TimeStamp + return x.Timestamp } return nil } @@ -993,7 +993,7 @@ type PubSubConsumeEvent struct { RequestKey *string `protobuf:"bytes,2,opt,name=request_key,json=requestKey,proto3,oneof" json:"request_key,omitempty"` DestVerbModule *string `protobuf:"bytes,3,opt,name=dest_verb_module,json=destVerbModule,proto3,oneof" json:"dest_verb_module,omitempty"` DestVerbName *string `protobuf:"bytes,4,opt,name=dest_verb_name,json=destVerbName,proto3,oneof" json:"dest_verb_name,omitempty"` - TimeStamp *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=time_stamp,json=timeStamp,proto3" json:"time_stamp,omitempty"` + Timestamp *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Duration *durationpb.Duration `protobuf:"bytes,6,opt,name=duration,proto3" json:"duration,omitempty"` Topic string `protobuf:"bytes,7,opt,name=topic,proto3" json:"topic,omitempty"` Error *string `protobuf:"bytes,8,opt,name=error,proto3,oneof" json:"error,omitempty"` @@ -1057,9 +1057,9 @@ func (x *PubSubConsumeEvent) GetDestVerbName() string { return "" } -func (x *PubSubConsumeEvent) GetTimeStamp() *timestamppb.Timestamp { +func (x *PubSubConsumeEvent) GetTimestamp() *timestamppb.Timestamp { if x != nil { - return x.TimeStamp + return x.Timestamp } return nil } @@ -1090,7 +1090,7 @@ type Event struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - TimeStamp *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=time_stamp,json=timeStamp,proto3" json:"time_stamp,omitempty"` + Timestamp *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` // Unique ID for event. Id int64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` // Types that are assignable to Entry: @@ -1137,9 +1137,9 @@ func (*Event) Descriptor() ([]byte, []int) { return file_xyz_block_ftl_timeline_v1_event_proto_rawDescGZIP(), []int{9} } -func (x *Event) GetTimeStamp() *timestamppb.Timestamp { +func (x *Event) GetTimestamp() *timestamppb.Timestamp { if x != nil { - return x.TimeStamp + return x.Timestamp } return nil } @@ -1292,313 +1292,312 @@ var file_xyz_block_ftl_timeline_v1_event_proto_rawDesc = []byte{ 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x24, 0x78, 0x79, 0x7a, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb7, 0x03, 0x0a, 0x08, 0x4c, 0x6f, + 0x65, 0x6d, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb6, 0x03, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, - 0x88, 0x01, 0x01, 0x12, 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1b, - 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x53, 0x0a, 0x0a, 0x61, - 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x33, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, - 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, - 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x05, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x88, 0x01, 0x01, - 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, - 0x0e, 0x0a, 0x0c, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x42, - 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x22, 0x95, 0x04, 0x0a, 0x09, 0x43, 0x61, 0x6c, 0x6c, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x6c, 0x6f, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0d, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x39, - 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, - 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x49, 0x0a, 0x0f, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, - 0x74, 0x6c, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, - 0x48, 0x01, 0x52, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x62, 0x52, 0x65, - 0x66, 0x88, 0x01, 0x01, 0x12, 0x4e, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, - 0x74, 0x6c, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, - 0x52, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x65, 0x72, - 0x62, 0x52, 0x65, 0x66, 0x12, 0x35, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, - 0x48, 0x02, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x05, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x72, 0x65, 0x66, 0x42, 0x08, 0x0a, 0x06, 0x5f, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x4a, - 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0xb8, 0x01, 0x0a, 0x16, - 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, - 0x75, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, - 0x75, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, - 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6d, 0x69, 0x6e, - 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x73, 0x12, 0x1f, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, - 0x70, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x72, 0x65, - 0x70, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x22, 0x79, 0x0a, 0x16, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x72, 0x65, 0x76, 0x5f, 0x6d, 0x69, - 0x6e, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0f, 0x70, 0x72, 0x65, 0x76, 0x4d, 0x69, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x73, 0x22, 0x8e, 0x04, 0x0a, 0x0c, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x65, 0x70, 0x6c, - 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, - 0x52, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x12, - 0x37, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, - 0x6c, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x52, - 0x07, 0x76, 0x65, 0x72, 0x62, 0x52, 0x65, 0x66, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x70, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x6d, 0x70, - 0x12, 0x35, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, - 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x22, 0xe6, 0x02, 0x0a, 0x12, 0x43, 0x72, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, - 0x75, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, - 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, - 0x12, 0x37, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, - 0x74, 0x6c, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, - 0x52, 0x07, 0x76, 0x65, 0x72, 0x62, 0x52, 0x65, 0x66, 0x12, 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, - 0x65, 0x5f, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, - 0x74, 0x61, 0x6d, 0x70, 0x12, 0x35, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x0c, 0x73, - 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x73, - 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, - 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, - 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, - 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x9c, 0x03, 0x0a, 0x11, - 0x41, 0x73, 0x79, 0x6e, 0x63, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x65, 0x70, 0x6c, 0x6f, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, - 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x12, 0x37, - 0x0a, 0x08, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x88, 0x01, 0x01, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1b, 0x0a, + 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x53, 0x0a, 0x0a, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, + 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x74, + 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x05, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x88, 0x01, 0x01, 0x1a, + 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0e, + 0x0a, 0x0c, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x42, 0x08, + 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, + 0x63, 0x6b, 0x22, 0x94, 0x04, 0x0a, 0x09, 0x43, 0x61, 0x6c, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x38, 0x0a, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x49, 0x0a, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, - 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x52, 0x07, - 0x76, 0x65, 0x72, 0x62, 0x52, 0x65, 0x66, 0x12, 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, - 0x6d, 0x70, 0x12, 0x35, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5a, 0x0a, 0x10, 0x61, 0x73, 0x79, - 0x6e, 0x63, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, - 0x66, 0x74, 0x6c, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x73, 0x79, 0x6e, 0x63, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x61, 0x73, 0x79, 0x6e, 0x63, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, - 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, - 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xf1, 0x02, 0x0a, 0x12, 0x50, - 0x75, 0x62, 0x53, 0x75, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x65, 0x70, 0x6c, 0x6f, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, - 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x12, 0x37, - 0x0a, 0x08, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x48, 0x01, + 0x52, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x62, 0x52, 0x65, 0x66, 0x88, + 0x01, 0x01, 0x12, 0x4e, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, - 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x52, 0x07, - 0x76, 0x65, 0x72, 0x62, 0x52, 0x65, 0x66, 0x12, 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, - 0x6d, 0x70, 0x12, 0x35, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, + 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x52, 0x12, + 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x62, 0x52, + 0x65, 0x66, 0x12, 0x35, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, - 0x69, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, - 0x18, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x5f, 0x6b, 0x65, 0x79, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xa0, - 0x03, 0x0a, 0x12, 0x50, 0x75, 0x62, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, - 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0b, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x88, - 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x10, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x62, 0x5f, - 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0e, - 0x64, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x62, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x88, 0x01, - 0x01, 0x12, 0x29, 0x0a, 0x0e, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x0c, 0x64, 0x65, 0x73, - 0x74, 0x56, 0x65, 0x72, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x39, 0x0a, 0x0a, - 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, - 0x6d, 0x65, 0x53, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x35, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, - 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, - 0x6f, 0x70, 0x69, 0x63, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, - 0x0e, 0x0a, 0x0c, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x42, - 0x13, 0x0a, 0x11, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x6d, 0x6f, - 0x64, 0x75, 0x6c, 0x65, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x76, 0x65, - 0x72, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x22, 0xba, 0x06, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x74, - 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, - 0x65, 0x53, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x37, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, - 0x66, 0x74, 0x6c, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x6f, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, - 0x3a, 0x0a, 0x04, 0x63, 0x61, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, - 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x74, 0x69, - 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x04, 0x63, 0x61, 0x6c, 0x6c, 0x12, 0x62, 0x0a, 0x12, 0x64, - 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x64, 0x65, - 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, - 0x62, 0x0a, 0x12, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x78, 0x79, + 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x19, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, + 0x63, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x72, 0x65, 0x66, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x4a, 0x04, 0x08, + 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0xb8, 0x01, 0x0a, 0x16, 0x44, 0x65, + 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, + 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, + 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x52, 0x65, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x73, 0x12, 0x1f, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, + 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x72, 0x65, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x64, 0x22, 0x79, 0x0a, 0x16, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x72, 0x65, 0x76, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, + 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, + 0x70, 0x72, 0x65, 0x76, 0x4d, 0x69, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x73, 0x22, + 0x8d, 0x04, 0x0a, 0x0c, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x12, 0x37, 0x0a, + 0x08, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x62, 0x52, 0x65, 0x66, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, + 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x35, 0x0a, + 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, + 0x0a, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, + 0xe5, 0x02, 0x0a, 0x12, 0x43, 0x72, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x37, 0x0a, + 0x08, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x62, 0x52, 0x65, 0x66, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x12, 0x35, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x0c, 0x73, 0x63, 0x68, 0x65, 0x64, + 0x75, 0x6c, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x73, 0x63, 0x68, 0x65, 0x64, + 0x75, 0x6c, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, + 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, + 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, + 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x9b, 0x03, 0x0a, 0x11, 0x41, 0x73, 0x79, 0x6e, + 0x63, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x0a, + 0x0e, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x12, 0x37, 0x0a, 0x08, 0x76, 0x65, + 0x72, 0x62, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, + 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x73, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x52, 0x07, 0x76, 0x65, 0x72, 0x62, + 0x52, 0x65, 0x66, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x35, 0x0a, + 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5a, 0x0a, 0x10, 0x61, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x65, 0x76, + 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, + 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x74, + 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x79, 0x6e, 0x63, + 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x0e, 0x61, 0x73, 0x79, 0x6e, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x01, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x42, 0x08, 0x0a, 0x06, 0x5f, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xf0, 0x02, 0x0a, 0x12, 0x50, 0x75, 0x62, 0x53, 0x75, 0x62, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, + 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x12, 0x37, 0x0a, 0x08, 0x76, 0x65, 0x72, + 0x62, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x79, + 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x52, 0x07, 0x76, 0x65, 0x72, 0x62, 0x52, + 0x65, 0x66, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x35, 0x0a, 0x08, + 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x01, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0e, + 0x0a, 0x0c, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x42, 0x08, + 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x9f, 0x03, 0x0a, 0x12, 0x50, 0x75, 0x62, + 0x53, 0x75, 0x62, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x10, + 0x64, 0x65, 0x73, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0e, 0x64, 0x65, 0x73, 0x74, 0x56, 0x65, + 0x72, 0x62, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, 0x0e, 0x64, + 0x65, 0x73, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x62, 0x4e, + 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x12, 0x35, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x19, 0x0a, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x64, 0x65, 0x73, + 0x74, 0x5f, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x42, 0x11, 0x0a, + 0x0f, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xb9, 0x06, 0x0a, 0x05, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x37, + 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x74, 0x69, 0x6d, 0x65, - 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x11, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x07, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, - 0x07, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x56, 0x0a, 0x0e, 0x63, 0x72, 0x6f, 0x6e, - 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x2d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, - 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, - 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, - 0x00, 0x52, 0x0d, 0x63, 0x72, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, - 0x12, 0x53, 0x0a, 0x0d, 0x61, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x61, 0x73, 0x79, 0x6e, 0x63, 0x45, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, 0x56, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x5f, - 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, + 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x3a, 0x0a, 0x04, 0x63, 0x61, 0x6c, 0x6c, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x04, 0x63, + 0x61, 0x6c, 0x6c, 0x12, 0x62, 0x0a, 0x12, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x31, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, + 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6c, + 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x62, 0x0a, 0x12, 0x64, 0x65, 0x70, 0x6c, 0x6f, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, + 0x66, 0x74, 0x6c, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x07, 0x69, + 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, + 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x74, 0x69, 0x6d, + 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x07, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, + 0x12, 0x56, 0x0a, 0x0e, 0x63, 0x72, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, + 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x72, 0x6f, 0x6e, 0x53, + 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x12, 0x53, 0x0a, 0x0d, 0x61, 0x73, 0x79, 0x6e, + 0x63, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2c, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, + 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x79, 0x6e, + 0x63, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, + 0x0c, 0x61, 0x73, 0x79, 0x6e, 0x63, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, 0x56, 0x0a, + 0x0e, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x50, 0x75, 0x62, 0x53, 0x75, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x73, 0x68, 0x12, 0x56, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x5f, + 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, 0x53, 0x75, 0x62, - 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, - 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x12, 0x56, 0x0a, - 0x0e, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x50, 0x75, 0x62, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x43, 0x6f, - 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2a, 0xa9, - 0x02, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, - 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, - 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x45, 0x56, 0x45, 0x4e, - 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4c, 0x4f, 0x47, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, - 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10, - 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x44, 0x45, 0x50, 0x4c, 0x4f, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, - 0x45, 0x44, 0x10, 0x03, 0x12, 0x21, 0x0a, 0x1d, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x5f, 0x44, 0x45, 0x50, 0x4c, 0x4f, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x55, 0x50, - 0x44, 0x41, 0x54, 0x45, 0x44, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x45, 0x56, 0x45, 0x4e, 0x54, - 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x05, 0x12, - 0x1d, 0x0a, 0x19, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x52, - 0x4f, 0x4e, 0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x44, 0x10, 0x06, 0x12, 0x1c, - 0x0a, 0x18, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x53, 0x59, - 0x4e, 0x43, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x10, 0x07, 0x12, 0x1d, 0x0a, 0x19, - 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x53, 0x55, - 0x42, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x10, 0x08, 0x12, 0x1d, 0x0a, 0x19, 0x45, - 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x53, 0x55, 0x42, - 0x5f, 0x43, 0x4f, 0x4e, 0x53, 0x55, 0x4d, 0x45, 0x10, 0x09, 0x2a, 0x89, 0x01, 0x0a, 0x15, 0x41, - 0x73, 0x79, 0x6e, 0x63, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x24, 0x41, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x45, 0x58, - 0x45, 0x43, 0x55, 0x54, 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x21, - 0x0a, 0x1d, 0x41, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x5f, - 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x52, 0x4f, 0x4e, 0x10, - 0x01, 0x12, 0x23, 0x0a, 0x1f, 0x41, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, - 0x54, 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, - 0x42, 0x53, 0x55, 0x42, 0x10, 0x02, 0x2a, 0x8c, 0x01, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x12, 0x19, 0x0a, 0x15, 0x4c, 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, - 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x13, - 0x0a, 0x0f, 0x4c, 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x54, 0x52, 0x41, 0x43, - 0x45, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, - 0x5f, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x4f, 0x47, 0x5f, - 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x09, 0x12, 0x12, 0x0a, 0x0e, - 0x4c, 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x0d, - 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x45, 0x52, - 0x52, 0x4f, 0x52, 0x10, 0x11, 0x42, 0x52, 0x50, 0x01, 0x5a, 0x4e, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x42, 0x44, 0x35, 0x34, 0x35, 0x36, 0x36, 0x39, 0x37, - 0x35, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x78, 0x79, 0x7a, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x66, - 0x74, 0x6c, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x74, - 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, + 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x42, 0x07, 0x0a, + 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2a, 0xa9, 0x02, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x12, 0x0a, 0x0e, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4c, + 0x4f, 0x47, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x45, 0x56, 0x45, + 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x50, 0x4c, 0x4f, 0x59, 0x4d, 0x45, + 0x4e, 0x54, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x21, 0x0a, 0x1d, + 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x50, 0x4c, 0x4f, + 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x44, 0x10, 0x04, 0x12, + 0x16, 0x0a, 0x12, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, + 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x05, 0x12, 0x1d, 0x0a, 0x19, 0x45, 0x56, 0x45, 0x4e, 0x54, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x52, 0x4f, 0x4e, 0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, + 0x55, 0x4c, 0x45, 0x44, 0x10, 0x06, 0x12, 0x1c, 0x0a, 0x18, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, + 0x54, 0x45, 0x10, 0x07, 0x12, 0x1d, 0x0a, 0x19, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x53, 0x55, 0x42, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, + 0x48, 0x10, 0x08, 0x12, 0x1d, 0x0a, 0x19, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x50, 0x55, 0x42, 0x53, 0x55, 0x42, 0x5f, 0x43, 0x4f, 0x4e, 0x53, 0x55, 0x4d, 0x45, + 0x10, 0x09, 0x2a, 0x89, 0x01, 0x0a, 0x15, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x45, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x24, + 0x41, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x5f, 0x45, 0x56, + 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x21, 0x0a, 0x1d, 0x41, 0x53, 0x59, 0x4e, 0x43, 0x5f, + 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x43, 0x52, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x23, 0x0a, 0x1f, 0x41, 0x53, 0x59, + 0x4e, 0x43, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x53, 0x55, 0x42, 0x10, 0x02, 0x2a, 0x8c, + 0x01, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x19, 0x0a, 0x15, 0x4c, + 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x4f, 0x47, 0x5f, 0x4c, 0x45, + 0x56, 0x45, 0x4c, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x4c, + 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x05, + 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x49, 0x4e, + 0x46, 0x4f, 0x10, 0x09, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x4f, 0x47, 0x5f, 0x4c, 0x45, 0x56, 0x45, + 0x4c, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x0d, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x4f, 0x47, 0x5f, + 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x11, 0x42, 0x52, 0x50, + 0x01, 0x5a, 0x4e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x42, + 0x44, 0x35, 0x34, 0x35, 0x36, 0x36, 0x39, 0x37, 0x35, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x62, 0x61, + 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x78, 0x79, 0x7a, + 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x6c, + 0x69, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x76, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1635,29 +1634,29 @@ var file_xyz_block_ftl_timeline_v1_event_proto_goTypes = []any{ (*durationpb.Duration)(nil), // 16: google.protobuf.Duration } var file_xyz_block_ftl_timeline_v1_event_proto_depIdxs = []int32{ - 14, // 0: xyz.block.ftl.timeline.v1.LogEvent.time_stamp:type_name -> google.protobuf.Timestamp + 14, // 0: xyz.block.ftl.timeline.v1.LogEvent.timestamp:type_name -> google.protobuf.Timestamp 13, // 1: xyz.block.ftl.timeline.v1.LogEvent.attributes:type_name -> xyz.block.ftl.timeline.v1.LogEvent.AttributesEntry - 14, // 2: xyz.block.ftl.timeline.v1.CallEvent.time_stamp:type_name -> google.protobuf.Timestamp + 14, // 2: xyz.block.ftl.timeline.v1.CallEvent.timestamp:type_name -> google.protobuf.Timestamp 15, // 3: xyz.block.ftl.timeline.v1.CallEvent.source_verb_ref:type_name -> xyz.block.ftl.schema.v1.Ref 15, // 4: xyz.block.ftl.timeline.v1.CallEvent.destination_verb_ref:type_name -> xyz.block.ftl.schema.v1.Ref 16, // 5: xyz.block.ftl.timeline.v1.CallEvent.duration:type_name -> google.protobuf.Duration 15, // 6: xyz.block.ftl.timeline.v1.IngressEvent.verb_ref:type_name -> xyz.block.ftl.schema.v1.Ref - 14, // 7: xyz.block.ftl.timeline.v1.IngressEvent.time_stamp:type_name -> google.protobuf.Timestamp + 14, // 7: xyz.block.ftl.timeline.v1.IngressEvent.timestamp:type_name -> google.protobuf.Timestamp 16, // 8: xyz.block.ftl.timeline.v1.IngressEvent.duration:type_name -> google.protobuf.Duration 15, // 9: xyz.block.ftl.timeline.v1.CronScheduledEvent.verb_ref:type_name -> xyz.block.ftl.schema.v1.Ref - 14, // 10: xyz.block.ftl.timeline.v1.CronScheduledEvent.time_stamp:type_name -> google.protobuf.Timestamp + 14, // 10: xyz.block.ftl.timeline.v1.CronScheduledEvent.timestamp:type_name -> google.protobuf.Timestamp 16, // 11: xyz.block.ftl.timeline.v1.CronScheduledEvent.duration:type_name -> google.protobuf.Duration 14, // 12: xyz.block.ftl.timeline.v1.CronScheduledEvent.scheduled_at:type_name -> google.protobuf.Timestamp 15, // 13: xyz.block.ftl.timeline.v1.AsyncExecuteEvent.verb_ref:type_name -> xyz.block.ftl.schema.v1.Ref - 14, // 14: xyz.block.ftl.timeline.v1.AsyncExecuteEvent.time_stamp:type_name -> google.protobuf.Timestamp + 14, // 14: xyz.block.ftl.timeline.v1.AsyncExecuteEvent.timestamp:type_name -> google.protobuf.Timestamp 16, // 15: xyz.block.ftl.timeline.v1.AsyncExecuteEvent.duration:type_name -> google.protobuf.Duration 1, // 16: xyz.block.ftl.timeline.v1.AsyncExecuteEvent.async_event_type:type_name -> xyz.block.ftl.timeline.v1.AsyncExecuteEventType 15, // 17: xyz.block.ftl.timeline.v1.PubSubPublishEvent.verb_ref:type_name -> xyz.block.ftl.schema.v1.Ref - 14, // 18: xyz.block.ftl.timeline.v1.PubSubPublishEvent.time_stamp:type_name -> google.protobuf.Timestamp + 14, // 18: xyz.block.ftl.timeline.v1.PubSubPublishEvent.timestamp:type_name -> google.protobuf.Timestamp 16, // 19: xyz.block.ftl.timeline.v1.PubSubPublishEvent.duration:type_name -> google.protobuf.Duration - 14, // 20: xyz.block.ftl.timeline.v1.PubSubConsumeEvent.time_stamp:type_name -> google.protobuf.Timestamp + 14, // 20: xyz.block.ftl.timeline.v1.PubSubConsumeEvent.timestamp:type_name -> google.protobuf.Timestamp 16, // 21: xyz.block.ftl.timeline.v1.PubSubConsumeEvent.duration:type_name -> google.protobuf.Duration - 14, // 22: xyz.block.ftl.timeline.v1.Event.time_stamp:type_name -> google.protobuf.Timestamp + 14, // 22: xyz.block.ftl.timeline.v1.Event.timestamp:type_name -> google.protobuf.Timestamp 3, // 23: xyz.block.ftl.timeline.v1.Event.log:type_name -> xyz.block.ftl.timeline.v1.LogEvent 4, // 24: xyz.block.ftl.timeline.v1.Event.call:type_name -> xyz.block.ftl.timeline.v1.CallEvent 5, // 25: xyz.block.ftl.timeline.v1.Event.deployment_created:type_name -> xyz.block.ftl.timeline.v1.DeploymentCreatedEvent diff --git a/backend/protos/xyz/block/ftl/timeline/v1/event.proto b/backend/protos/xyz/block/ftl/timeline/v1/event.proto index bb4d03deab..6174c0f9c3 100644 --- a/backend/protos/xyz/block/ftl/timeline/v1/event.proto +++ b/backend/protos/xyz/block/ftl/timeline/v1/event.proto @@ -40,7 +40,7 @@ enum LogLevel { message LogEvent { string deployment_key = 1; optional string request_key = 2; - google.protobuf.Timestamp time_stamp = 3; + google.protobuf.Timestamp timestamp = 3; int32 log_level = 4; map attributes = 5; string message = 6; @@ -51,7 +51,7 @@ message LogEvent { message CallEvent { optional string request_key = 1; string deployment_key = 2; - google.protobuf.Timestamp time_stamp = 3; + google.protobuf.Timestamp timestamp = 3; optional ftl.schema.v1.Ref source_verb_ref = 11; ftl.schema.v1.Ref destination_verb_ref = 12; google.protobuf.Duration duration = 6; @@ -84,7 +84,7 @@ message IngressEvent { string method = 4; string path = 5; int32 status_code = 7; - google.protobuf.Timestamp time_stamp = 8; + google.protobuf.Timestamp timestamp = 8; google.protobuf.Duration duration = 9; string request = 10; string request_header = 11; @@ -96,7 +96,7 @@ message IngressEvent { message CronScheduledEvent { string deployment_key = 1; ftl.schema.v1.Ref verb_ref = 2; - google.protobuf.Timestamp time_stamp = 3; + google.protobuf.Timestamp timestamp = 3; google.protobuf.Duration duration = 4; google.protobuf.Timestamp scheduled_at = 5; string schedule = 6; @@ -107,7 +107,7 @@ message AsyncExecuteEvent { string deployment_key = 1; optional string request_key = 2; ftl.schema.v1.Ref verb_ref = 3; - google.protobuf.Timestamp time_stamp = 4; + google.protobuf.Timestamp timestamp = 4; google.protobuf.Duration duration = 5; AsyncExecuteEventType async_event_type = 6; optional string error = 7; @@ -117,7 +117,7 @@ message PubSubPublishEvent { string deployment_key = 1; optional string request_key = 2; ftl.schema.v1.Ref verb_ref = 3; - google.protobuf.Timestamp time_stamp = 4; + google.protobuf.Timestamp timestamp = 4; google.protobuf.Duration duration = 5; string topic = 6; string request = 7; @@ -129,14 +129,14 @@ message PubSubConsumeEvent { optional string request_key = 2; optional string dest_verb_module = 3; optional string dest_verb_name = 4; - google.protobuf.Timestamp time_stamp = 5; + google.protobuf.Timestamp timestamp = 5; google.protobuf.Duration duration = 6; string topic = 7; optional string error = 8; } message Event { - google.protobuf.Timestamp time_stamp = 1; + google.protobuf.Timestamp timestamp = 1; // Unique ID for event. int64 id = 2; oneof entry { diff --git a/backend/timeline/events_async.go b/backend/timeline/events_async.go new file mode 100644 index 0000000000..d047e70a38 --- /dev/null +++ b/backend/timeline/events_async.go @@ -0,0 +1,102 @@ +package timeline + +import ( + "time" + + timelinepb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1" + "github.com/TBD54566975/ftl/internal/model" + "github.com/TBD54566975/ftl/internal/schema" + "github.com/alecthomas/types/optional" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// type AsyncExecuteEvent struct { +// ID int64 +// Duration time.Duration +// AsyncExecute +// } + +// func (e *AsyncExecuteEvent) GetID() int64 { return e.ID } +// func (e *AsyncExecuteEvent) event() {} + +type AsyncExecuteEventType string + +const ( + AsyncExecuteEventTypeUnkown AsyncExecuteEventType = "unknown" + AsyncExecuteEventTypeCron AsyncExecuteEventType = "cron" + AsyncExecuteEventTypePubSub AsyncExecuteEventType = "pubsub" +) + +type AsyncExecute struct { + DeploymentKey model.DeploymentKey + RequestKey optional.Option[string] + EventType AsyncExecuteEventType + Verb schema.Ref + Time time.Time + Error optional.Option[string] +} + +var _ Event = AsyncExecute{} + +func (AsyncExecute) clientEvent() {} +func (a AsyncExecute) ToReq() *timelinepb.CreateEventRequest { + return &timelinepb.CreateEventRequest{ + Entry: &timelinepb.CreateEventRequest_AsyncExecute{ + AsyncExecute: &timelinepb.AsyncExecuteEvent{ + DeploymentKey: a.DeploymentKey.String(), + RequestKey: a.RequestKey.Ptr(), + Timestamp: timestamppb.New(a.Time), + Error: a.Error.Ptr(), + // TODO: fill in + // VerbRef *v1.Ref `protobuf:"bytes,3,opt,name=verb_ref,json=verbRef,proto3" json:"verb_ref,omitempty"` + // Duration *durationpb.Duration `protobuf:"bytes,5,opt,name=duration,proto3" json:"duration,omitempty"` + // AsyncEventType AsyncExecuteEventType `protobuf:"varint,6,opt,name=async_event_type,json=asyncEventType,proto3,enum=xyz.block.ftl.timeline.v1.AsyncExecuteEventType" json:"async_event_type,omitempty"` + }, + }, + } +} + +// func (e *AsyncExecute) toEvent() (Event, error) { //nolint:unparam +// return &AsyncExecuteEvent{ +// AsyncExecute: *e, +// Duration: time.Since(e.Time), +// }, nil +// } + +// type eventAsyncExecuteJSON struct { +// DurationMS int64 `json:"duration_ms"` +// EventType AsyncExecuteEventType `json:"event_type"` +// Error optional.Option[string] `json:"error,omitempty"` +// } + +// func (s *Service) insertAsyncExecuteEvent(ctx context.Context, querier sql.Querier, event *AsyncExecuteEvent) error { +// asyncJSON := eventAsyncExecuteJSON{ +// DurationMS: event.Duration.Milliseconds(), +// EventType: event.EventType, +// Error: event.Error, +// } + +// data, err := json.Marshal(asyncJSON) +// if err != nil { +// return fmt.Errorf("failed to marshal async execute event: %w", err) +// } + +// var payload ftlencryption.EncryptedTimelineColumn +// err = s.encryption.EncryptJSON(json.RawMessage(data), &payload) +// if err != nil { +// return fmt.Errorf("failed to encrypt cron JSON: %w", err) +// } + +// err = libdal.TranslatePGError(querier.InsertTimelineAsyncExecuteEvent(ctx, sql.InsertTimelineAsyncExecuteEventParams{ +// DeploymentKey: event.DeploymentKey, +// RequestKey: event.RequestKey, +// TimeStamp: event.Time, +// Module: event.Verb.Module, +// Verb: event.Verb.Name, +// Payload: payload, +// })) +// if err != nil { +// return fmt.Errorf("failed to insert async execute event: %w", err) +// } +// return err +// } diff --git a/backend/timeline/events_call.go b/backend/timeline/events_call.go new file mode 100644 index 0000000000..ab59e41c9d --- /dev/null +++ b/backend/timeline/events_call.go @@ -0,0 +1,210 @@ +package timeline + +import ( + "time" + + timelinepb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1" + ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" + "github.com/TBD54566975/ftl/internal/model" + "github.com/TBD54566975/ftl/internal/schema" + "github.com/alecthomas/types/optional" + "github.com/alecthomas/types/result" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// type CallEvent struct { +// ID int64 +// DeploymentKey model.DeploymentKey +// RequestKey optional.Option[model.RequestKey] +// ParentRequestKey optional.Option[model.RequestKey] +// Time time.Time +// SourceVerb optional.Option[schema.Ref] +// DestVerb schema.Ref +// Duration time.Duration +// Request json.RawMessage +// Response json.RawMessage +// Error optional.Option[string] +// Stack optional.Option[string] +// } + +// func (e *CallEvent) GetID() int64 { return e.ID } +// func (e *CallEvent) event() {} + +// type eventCallJSON struct { +// DurationMS int64 `json:"duration_ms"` +// Request json.RawMessage `json:"request"` +// Response json.RawMessage `json:"response"` +// Error optional.Option[string] `json:"error,omitempty"` +// Stack optional.Option[string] `json:"stack,omitempty"` +// } + +type Call struct { + DeploymentKey model.DeploymentKey + RequestKey model.RequestKey + ParentRequestKey optional.Option[model.RequestKey] + StartTime time.Time + DestVerb *schema.Ref + Callers []*schema.Ref + Request *ftlv1.CallRequest + Response result.Result[*ftlv1.CallResponse] +} + +func (Call) clientEvent() {} +func (c Call) ToReq() *timelinepb.CreateEventRequest { + requestKey := c.RequestKey.String() + + var respError *string + var response string + _, err := c.Response.Result() + if err != nil { + errStr := err.Error() + respError = &errStr + } else { + // TODO: fill in + } + return &timelinepb.CreateEventRequest{ + Entry: &timelinepb.CreateEventRequest_Call{ + Call: &timelinepb.CallEvent{ + RequestKey: &requestKey, + DeploymentKey: c.DeploymentKey.String(), + Timestamp: timestamppb.New(c.StartTime), + Response: response, + Error: respError, + // TODO: fill in + // SourceVerbRef *v1.Ref `protobuf:"bytes,11,opt,name=source_verb_ref,json=sourceVerbRef,proto3,oneof" json:"source_verb_ref,omitempty"` + // DestinationVerbRef *v1.Ref `protobuf:"bytes,12,opt,name=destination_verb_ref,json=destinationVerbRef,proto3" json:"destination_verb_ref,omitempty"` + // Duration *durationpb.Duration `protobuf:"bytes,6,opt,name=duration,proto3" json:"duration,omitempty"` + // Request string `protobuf:"bytes,7,opt,name=request,proto3" json:"request,omitempty"` + // Stack *string `protobuf:"bytes,10,opt,name=stack,proto3,oneof" json:"stack,omitempty"` + }, + }, + } +} + +// func (c *Call) toEvent() (Event, error) { return callToCallEvent(c), nil } //nolint:unparam + +// func (s *Service) insertCallEvent(ctx context.Context, querier sql.Querier, callEvent *CallEvent) error { +// var sourceModule, sourceVerb optional.Option[string] +// if sr, ok := callEvent.SourceVerb.Get(); ok { +// sourceModule, sourceVerb = optional.Some(sr.Module), optional.Some(sr.Name) +// } + +// var requestKey optional.Option[string] +// if rn, ok := callEvent.RequestKey.Get(); ok { +// requestKey = optional.Some(rn.String()) +// } + +// var parentRequestKey optional.Option[string] +// if pr, ok := callEvent.ParentRequestKey.Get(); ok { +// parentRequestKey = optional.Some(pr.String()) +// } + +// callJSON := eventCallJSON{ +// DurationMS: callEvent.Duration.Milliseconds(), +// Request: callEvent.Request, +// Response: callEvent.Response, +// Error: callEvent.Error, +// Stack: callEvent.Stack, +// } + +// data, err := json.Marshal(callJSON) +// if err != nil { +// return fmt.Errorf("failed to marshal call event: %w", err) +// } + +// var payload ftlencryption.EncryptedTimelineColumn +// err = s.encryption.EncryptJSON(json.RawMessage(data), &payload) +// if err != nil { +// return fmt.Errorf("failed to encrypt call event: %w", err) +// } + +// err = libdal.TranslatePGError(querier.InsertTimelineCallEvent(ctx, sql.InsertTimelineCallEventParams{ +// DeploymentKey: callEvent.DeploymentKey, +// RequestKey: requestKey, +// ParentRequestKey: parentRequestKey, +// TimeStamp: callEvent.Time, +// SourceModule: sourceModule, +// SourceVerb: sourceVerb, +// DestModule: callEvent.DestVerb.Module, +// DestVerb: callEvent.DestVerb.Name, +// Payload: payload, +// })) +// if err != nil { +// return fmt.Errorf("failed to insert call event: %w", err) +// } +// return nil +// } + +// func callToCallEvent(call *Call) *CallEvent { +// var sourceVerb optional.Option[schema.Ref] +// if len(call.Callers) > 0 { +// sourceVerb = optional.Some(*call.Callers[0]) +// } + +// var errorStr optional.Option[string] +// var stack optional.Option[string] +// var responseBody []byte + +// switch response := call.Response.(type) { +// case either.Left[*ftlv1.CallResponse, error]: +// resp := response.Get() +// responseBody = resp.GetBody() +// if callError := resp.GetError(); callError != nil { +// errorStr = optional.Some(callError.Message) +// stack = optional.Ptr(callError.Stack) +// } +// case either.Right[*ftlv1.CallResponse, error]: +// callError := response.Get() +// errorStr = optional.Some(callError.Error()) +// } + +// return &CallEvent{ +// Time: call.StartTime, +// DeploymentKey: call.DeploymentKey, +// RequestKey: optional.Some(call.RequestKey), +// ParentRequestKey: call.ParentRequestKey, +// Duration: time.Since(call.StartTime), +// SourceVerb: sourceVerb, +// DestVerb: *call.DestVerb, +// Request: call.Request.GetBody(), +// Response: responseBody, +// Error: errorStr, +// Stack: stack, +// } +// } + +// func CallEventToCallForTesting(event *CallEvent) *Call { +// var response either.Either[*ftlv1.CallResponse, error] +// if eventErr, ok := event.Error.Get(); ok { +// response = either.RightOf[*ftlv1.CallResponse](errors.New(eventErr)) +// } else { +// response = either.LeftOf[error](&ftlv1.CallResponse{ +// Response: &ftlv1.CallResponse_Body{ +// Body: event.Response, +// }, +// }) +// } + +// var requestKey model.RequestKey +// if key, ok := event.RequestKey.Get(); ok { +// requestKey = key +// } else { +// requestKey = model.RequestKey{} +// } + +// callers := []*schema.Ref{} +// if ref, ok := event.SourceVerb.Get(); ok { +// callers = []*schema.Ref{&ref} +// } + +// return &Call{ +// DeploymentKey: event.DeploymentKey, +// RequestKey: requestKey, +// ParentRequestKey: event.ParentRequestKey, +// StartTime: event.Time, +// DestVerb: &event.DestVerb, +// Callers: callers, +// Request: &ftlv1.CallRequest{Body: event.Request}, +// Response: response, +// } +// } diff --git a/backend/timeline/events_cron.go b/backend/timeline/events_cron.go new file mode 100644 index 0000000000..f1f9948478 --- /dev/null +++ b/backend/timeline/events_cron.go @@ -0,0 +1,66 @@ +package timeline + +// type CronScheduledEvent struct { +// ID int64 +// Duration time.Duration +// CronScheduled +// } + +// func (e *CronScheduledEvent) GetID() int64 { return e.ID } +// func (e *CronScheduledEvent) event() {} + +// type CronScheduled struct { +// DeploymentKey model.DeploymentKey +// Verb schema.Ref + +// Time time.Time +// ScheduledAt time.Time +// Schedule string +// Error optional.Option[string] +// } + +// func (e *CronScheduled) toEvent() (Event, error) { //nolint:unparam +// return &CronScheduledEvent{ +// CronScheduled: *e, +// Duration: time.Since(e.Time), +// }, nil +// } + +// type eventCronScheduledJSON struct { +// DurationMS int64 `json:"duration_ms"` +// ScheduledAt time.Time `json:"scheduled_at"` +// Schedule string `json:"schedule"` +// Error optional.Option[string] `json:"error,omitempty"` +// } + +// func (s *Service) insertCronScheduledEvent(ctx context.Context, querier sql.Querier, event *CronScheduledEvent) error { +// cronJSON := eventCronScheduledJSON{ +// DurationMS: event.Duration.Milliseconds(), +// ScheduledAt: event.ScheduledAt, +// Schedule: event.Schedule, +// Error: event.Error, +// } + +// data, err := json.Marshal(cronJSON) +// if err != nil { +// return fmt.Errorf("failed to marshal cron JSON: %w", err) +// } + +// var payload ftlencryption.EncryptedTimelineColumn +// err = s.encryption.EncryptJSON(json.RawMessage(data), &payload) +// if err != nil { +// return fmt.Errorf("failed to encrypt cron JSON: %w", err) +// } + +// err = libdal.TranslatePGError(querier.InsertTimelineCronScheduledEvent(ctx, sql.InsertTimelineCronScheduledEventParams{ +// DeploymentKey: event.DeploymentKey, +// TimeStamp: event.Time, +// Module: event.Verb.Module, +// Verb: event.Verb.Name, +// Payload: payload, +// })) +// if err != nil { +// return fmt.Errorf("failed to insert cron event: %w", err) +// } +// return err +// } diff --git a/backend/timeline/events_deployment.go b/backend/timeline/events_deployment.go new file mode 100644 index 0000000000..370125735a --- /dev/null +++ b/backend/timeline/events_deployment.go @@ -0,0 +1,78 @@ +package timeline + +import ( + "time" + + timelinepb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1" + "github.com/TBD54566975/ftl/internal/model" + "github.com/alecthomas/types/optional" +) + +type DeploymentCreated struct { + DeploymentKey model.DeploymentKey + Time time.Time + Language string + ModuleName string + MinReplicas int + ReplacedDeployment optional.Option[model.DeploymentKey] +} + +var _ Event = DeploymentCreated{} + +func (DeploymentCreated) clientEvent() {} +func (d DeploymentCreated) ToReq() *timelinepb.CreateEventRequest { + var replaced *string + if r, ok := d.ReplacedDeployment.Get(); ok { + repl := r.String() + replaced = &repl + } + return &timelinepb.CreateEventRequest{ + Entry: &timelinepb.CreateEventRequest_DeploymentCreated{ + DeploymentCreated: &timelinepb.DeploymentCreatedEvent{ + Key: d.DeploymentKey.String(), + Language: d.Language, + ModuleName: d.ModuleName, + MinReplicas: int32(d.MinReplicas), + Replaced: replaced, + }, + }, + } +} + +// func (e *DeploymentCreatedEvent) GetID() int64 { return e.ID } +// func (e *DeploymentCreatedEvent) event() {} + +// type eventDeploymentUpdatedJSON struct { +// MinReplicas int `json:"min_replicas"` +// PrevMinReplicas int `json:"prev_min_replicas"` +// } + +type DeploymentUpdated struct { + DeploymentKey model.DeploymentKey + Time time.Time + MinReplicas int + PrevMinReplicas int +} + +var _ Event = DeploymentUpdated{} + +func (DeploymentUpdated) clientEvent() {} +func (d DeploymentUpdated) ToReq() *timelinepb.CreateEventRequest { + return &timelinepb.CreateEventRequest{ + Entry: &timelinepb.CreateEventRequest_DeploymentUpdated{ + DeploymentUpdated: &timelinepb.DeploymentUpdatedEvent{ + Key: d.DeploymentKey.String(), + MinReplicas: int32(d.MinReplicas), + PrevMinReplicas: int32(d.PrevMinReplicas), + }, + }, + } +} + +// func (e *DeploymentUpdatedEvent) GetID() int64 { return e.ID } +// func (e *DeploymentUpdatedEvent) event() {} + +// type eventDeploymentCreatedJSON struct { +// MinReplicas int `json:"min_replicas"` +// ReplacedDeployment optional.Option[model.DeploymentKey] `json:"replaced,omitempty"` +// } diff --git a/backend/timeline/events_ingress.go b/backend/timeline/events_ingress.go new file mode 100644 index 0000000000..2383471767 --- /dev/null +++ b/backend/timeline/events_ingress.go @@ -0,0 +1,165 @@ +package timeline + +import ( + "net/http" + "time" + + timelinepb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1" + "github.com/TBD54566975/ftl/internal/model" + "github.com/TBD54566975/ftl/internal/schema" + "github.com/alecthomas/types/optional" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// type IngressEvent struct { +// ID int64 +// DeploymentKey model.DeploymentKey +// RequestKey optional.Option[model.RequestKey] +// Verb schema.Ref +// Method string +// Path string + +// StatusCode int +// Time time.Time +// Duration time.Duration +// Request json.RawMessage +// RequestHeader json.RawMessage +// Response json.RawMessage +// ResponseHeader json.RawMessage +// Error optional.Option[string] +// } + +// func (e *IngressEvent) GetID() int64 { return e.ID } +// func (e *IngressEvent) event() {} + +// type eventIngressJSON struct { +// DurationMS int64 `json:"duration_ms"` +// Method string `json:"method"` +// Path string `json:"path"` +// StatusCode int `json:"status_code"` +// Request json.RawMessage `json:"request"` +// RequestHeader json.RawMessage `json:"req_header"` +// Response json.RawMessage `json:"response"` +// ResponseHeader json.RawMessage `json:"resp_header"` +// Error optional.Option[string] `json:"error,omitempty"` +// } + +type Ingress struct { + DeploymentKey model.DeploymentKey + RequestKey model.RequestKey + StartTime time.Time + Verb *schema.Ref + RequestMethod string + RequestPath string + RequestHeaders http.Header + ResponseStatus int + ResponseHeaders http.Header + RequestBody []byte + ResponseBody []byte + Error optional.Option[string] +} + +var _ Event = Ingress{} + +func (Ingress) clientEvent() {} +func (i Ingress) ToReq() *timelinepb.CreateEventRequest { + requestKey := i.RequestKey.String() + return &timelinepb.CreateEventRequest{ + Entry: &timelinepb.CreateEventRequest_Ingress{ + Ingress: &timelinepb.IngressEvent{ + DeploymentKey: i.DeploymentKey.String(), + RequestKey: &requestKey, + Timestamp: timestamppb.New(i.StartTime), + // TODO: fill in + // VerbRef *v1.Ref `protobuf:"bytes,3,opt,name=verb_ref,json=verbRef,proto3" json:"verb_ref,omitempty"` + // Method string `protobuf:"bytes,4,opt,name=method,proto3" json:"method,omitempty"` + // Path string `protobuf:"bytes,5,opt,name=path,proto3" json:"path,omitempty"` + // StatusCode int32 `protobuf:"varint,7,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"` + // Duration *durationpb.Duration `protobuf:"bytes,9,opt,name=duration,proto3" json:"duration,omitempty"` + // Request string `protobuf:"bytes,10,opt,name=request,proto3" json:"request,omitempty"` + // RequestHeader string `protobuf:"bytes,11,opt,name=request_header,json=requestHeader,proto3" json:"request_header,omitempty"` + // Response string `protobuf:"bytes,12,opt,name=response,proto3" json:"response,omitempty"` + // ResponseHeader string `protobuf:"bytes,13,opt,name=response_header,json=responseHeader,proto3" json:"response_header,omitempty"` + Error: i.Error.Ptr(), + }, + }, + } +} + +// func (ingress *Ingress) toEvent() (Event, error) { +// requestBody := ingress.RequestBody +// if len(requestBody) == 0 { +// requestBody = []byte("{}") +// } + +// responseBody := ingress.ResponseBody +// if len(responseBody) == 0 { +// responseBody = []byte("{}") +// } + +// reqHeaderBytes, err := json.Marshal(ingress.RequestHeaders) +// if err != nil { +// return nil, fmt.Errorf("failed to marshal request header: %w", err) +// } + +// respHeaderBytes, err := json.Marshal(ingress.ResponseHeaders) +// if err != nil { +// return nil, fmt.Errorf("failed to marshal response header: %w", err) +// } +// return &IngressEvent{ +// DeploymentKey: ingress.DeploymentKey, +// RequestKey: optional.Some(ingress.RequestKey), +// Verb: *ingress.Verb, +// Method: ingress.RequestMethod, +// Path: ingress.RequestPath, +// StatusCode: ingress.ResponseStatus, +// Time: ingress.StartTime, +// Duration: time.Since(ingress.StartTime), +// Request: requestBody, +// RequestHeader: reqHeaderBytes, +// Response: responseBody, +// ResponseHeader: respHeaderBytes, +// Error: ingress.Error, +// }, nil +// } + +// func (s *Service) insertHTTPIngress(ctx context.Context, querier sql.Querier, ingress *IngressEvent) error { +// ingressJSON := eventIngressJSON{ +// DurationMS: ingress.Duration.Milliseconds(), +// Method: ingress.Method, +// Path: ingress.Path, +// StatusCode: ingress.StatusCode, +// Request: ingress.Request, +// RequestHeader: ingress.RequestHeader, +// Response: ingress.Response, +// ResponseHeader: ingress.ResponseHeader, +// Error: ingress.Error, +// } + +// data, err := json.Marshal(ingressJSON) +// if err != nil { +// return fmt.Errorf("failed to marshal ingress JSON: %w", err) +// } + +// var payload ftlencryption.EncryptedTimelineColumn +// err = s.encryption.EncryptJSON(json.RawMessage(data), &payload) +// if err != nil { +// return fmt.Errorf("failed to encrypt ingress payload: %w", err) +// } + +// log.FromContext(ctx).Debugf("Inserting ingress event for %s %s", ingress.RequestKey, ingress.Path) + +// err = libdal.TranslatePGError(querier.InsertTimelineIngressEvent(ctx, sql.InsertTimelineIngressEventParams{ +// DeploymentKey: ingress.DeploymentKey, +// RequestKey: optional.Some(ingress.RequestKey.String()), +// TimeStamp: ingress.Time, +// Module: ingress.Verb.Module, +// Verb: ingress.Verb.Name, +// IngressType: "http", +// Payload: payload, +// })) +// if err != nil { +// return fmt.Errorf("failed to insert ingress event: %w", err) +// } +// return nil +// } diff --git a/backend/timeline/events_log.go b/backend/timeline/events_log.go new file mode 100644 index 0000000000..be95136554 --- /dev/null +++ b/backend/timeline/events_log.go @@ -0,0 +1,92 @@ +package timeline + +import ( + "time" + + timelinepb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1" + "github.com/TBD54566975/ftl/internal/model" + "github.com/alecthomas/types/optional" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type Log struct { + DeploymentKey model.DeploymentKey + RequestKey optional.Option[model.RequestKey] + Time time.Time + Level int32 + Attributes map[string]string + Message string + Error optional.Option[string] +} + +var _ Event = Log{} + +func (Log) clientEvent() {} +func (l Log) ToReq() *timelinepb.CreateEventRequest { + var requestKey *string + if r, ok := l.RequestKey.Get(); ok { + key := r.String() + requestKey = &key + } + return &timelinepb.CreateEventRequest{ + Entry: &timelinepb.CreateEventRequest_Log{ + Log: &timelinepb.LogEvent{ + DeploymentKey: l.DeploymentKey.String(), + RequestKey: requestKey, + Timestamp: timestamppb.New(l.Time), + LogLevel: l.Level, + Attributes: l.Attributes, + Message: l.Message, + Error: l.Error.Ptr(), + }, + }, + } +} + +// func (l *Log) toEvent() (Event, error) { return &LogEvent{Log: *l}, nil } //nolint:unparam + +// type LogEvent struct { +// ID int64 +// Log +// } + +// func (e *LogEvent) GetID() int64 { return e.ID } +// func (e *LogEvent) event() {} + +// type eventLogJSON struct { +// Message string `json:"message"` +// Attributes map[string]string `json:"attributes"` +// Error optional.Option[string] `json:"error,omitempty"` +// } + +// func (s *Service) insertLogEvent(ctx context.Context, querier sql.Querier, log *LogEvent) error { +// var requestKey optional.Option[string] +// if name, ok := log.RequestKey.Get(); ok { +// requestKey = optional.Some(name.String()) +// } + +// logJSON := eventLogJSON{ +// Message: log.Message, +// Attributes: log.Attributes, +// Error: log.Error, +// } + +// data, err := json.Marshal(logJSON) +// if err != nil { +// return fmt.Errorf("failed to marshal log event: %w", err) +// } + +// var encryptedPayload ftlencryption.EncryptedTimelineColumn +// err = s.encryption.EncryptJSON(json.RawMessage(data), &encryptedPayload) +// if err != nil { +// return fmt.Errorf("failed to encrypt log payload: %w", err) +// } + +// return libdal.TranslatePGError(querier.InsertTimelineLogEvent(ctx, sql.InsertTimelineLogEventParams{ +// DeploymentKey: log.DeploymentKey, +// RequestKey: requestKey, +// TimeStamp: log.Time, +// Level: log.Level, +// Payload: encryptedPayload, +// })) +// } diff --git a/backend/timeline/events_pubsub_consume.go b/backend/timeline/events_pubsub_consume.go new file mode 100644 index 0000000000..0d9758bda9 --- /dev/null +++ b/backend/timeline/events_pubsub_consume.go @@ -0,0 +1,104 @@ +package timeline + +import ( + "time" + + timelinepb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1" + "github.com/TBD54566975/ftl/internal/model" + "github.com/TBD54566975/ftl/internal/schema" + "github.com/alecthomas/types/optional" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// type PubSubConsumeEvent struct { +// ID int64 +// Duration time.Duration +// PubSubConsume +// } + +// func (e *PubSubConsumeEvent) GetID() int64 { return e.ID } +// func (e *PubSubConsumeEvent) event() {} + +type PubSubConsume struct { + DeploymentKey model.DeploymentKey + RequestKey optional.Option[string] + Time time.Time + DestVerb optional.Option[schema.RefKey] + Topic string + Error optional.Option[string] +} + +var _ Event = PubSubConsume{} + +func (PubSubConsume) clientEvent() {} +func (p PubSubConsume) ToReq() *timelinepb.CreateEventRequest { + return &timelinepb.CreateEventRequest{ + Entry: &timelinepb.CreateEventRequest_PubsubConsume{ + PubsubConsume: &timelinepb.PubSubConsumeEvent{ + DeploymentKey: p.DeploymentKey.String(), + RequestKey: p.RequestKey.Ptr(), + Timestamp: timestamppb.New(p.Time), + Topic: p.Topic, + Error: p.Error.Ptr(), + + // TODO: fill in + // DestVerbModule *string `protobuf:"bytes,3,opt,name=dest_verb_module,json=destVerbModule,proto3,oneof" json:"dest_verb_module,omitempty"` + // DestVerbName *string `protobuf:"bytes,4,opt,name=dest_verb_name,json=destVerbName,proto3,oneof" json:"dest_verb_name,omitempty"` + // Duration *durationpb.Duration `protobuf:"bytes,6,opt,name=duration,proto3" json:"duration,omitempty"` + }, + }, + } +} + +// func (e *PubSubConsume) toEvent() (Event, error) { //nolint:unparam +// return &PubSubConsumeEvent{ +// PubSubConsume: *e, +// Duration: time.Since(e.Time), +// }, nil +// } + +// type eventPubSubConsumeJSON struct { +// DurationMS int64 `json:"duration_ms"` +// Topic string `json:"topic"` +// Error optional.Option[string] `json:"error,omitempty"` +// } + +// func (s *Service) insertPubSubConsumeEvent(ctx context.Context, querier sql.Querier, event *PubSubConsumeEvent) error { +// pubsubJSON := eventPubSubConsumeJSON{ +// DurationMS: event.Duration.Milliseconds(), +// Topic: event.Topic, +// Error: event.Error, +// } + +// data, err := json.Marshal(pubsubJSON) +// if err != nil { +// return fmt.Errorf("failed to marshal pubsub event: %w", err) +// } + +// var payload ftlencryption.EncryptedTimelineColumn +// err = s.encryption.EncryptJSON(json.RawMessage(data), &payload) +// if err != nil { +// return fmt.Errorf("failed to encrypt cron JSON: %w", err) +// } + +// destModule := optional.None[string]() +// destVerb := optional.None[string]() +// if dv, ok := event.DestVerb.Get(); ok { +// destModule = optional.Some(dv.Module) +// destVerb = optional.Some(dv.Name) +// } + +// err = libdal.TranslatePGError(querier.InsertTimelinePubsubConsumeEvent(ctx, sql.InsertTimelinePubsubConsumeEventParams{ +// DeploymentKey: event.DeploymentKey, +// RequestKey: event.RequestKey, +// TimeStamp: event.Time, +// DestModule: destModule, +// DestVerb: destVerb, +// Topic: event.Topic, +// Payload: payload, +// })) +// if err != nil { +// return fmt.Errorf("failed to insert pubsub consume event: %w", err) +// } +// return err +// } diff --git a/backend/timeline/events_pubsub_publish.go b/backend/timeline/events_pubsub_publish.go new file mode 100644 index 0000000000..6da6533259 --- /dev/null +++ b/backend/timeline/events_pubsub_publish.go @@ -0,0 +1,103 @@ +package timeline + +import ( + "time" + + deployment "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/deployment/v1" + timelinepb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1" + "github.com/TBD54566975/ftl/internal/model" + "github.com/TBD54566975/ftl/internal/schema" + "github.com/alecthomas/types/optional" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// type PubSubPublishEvent struct { +// ID int64 +// Duration time.Duration +// Request json.RawMessage +// PubSubPublish +// } + +// func (e *PubSubPublishEvent) GetID() int64 { return e.ID } +// func (e *PubSubPublishEvent) event() {} + +type PubSubPublish struct { + DeploymentKey model.DeploymentKey + RequestKey optional.Option[string] + Time time.Time + SourceVerb schema.Ref + Topic string + Request *deployment.PublishEventRequest + Error optional.Option[string] +} + +var _ Event = PubSubPublish{} + +func (PubSubPublish) clientEvent() {} +func (p PubSubPublish) ToReq() *timelinepb.CreateEventRequest { + return &timelinepb.CreateEventRequest{ + Entry: &timelinepb.CreateEventRequest_PubsubPublish{ + PubsubPublish: &timelinepb.PubSubPublishEvent{ + DeploymentKey: p.DeploymentKey.String(), + RequestKey: p.RequestKey.Ptr(), + // TODO: verbRef + // VerbRef: *v1.Ref `protobuf:"bytes,3,opt,name=verb_ref,json=verbRef,proto3" json:"verb_ref,omitempty"` + Timestamp: timestamppb.New(p.Time), + Duration: durationpb.New(time.Since(p.Time)), + Topic: p.Topic, + // TODO: request + // Request: string `protobuf:"bytes,7,opt,name=request,proto3" json:"request,omitempty"` + Error: p.Error.Ptr(), + }, + }, + } +} + +// func (e *PubSubPublish) toEvent() (Event, error) { //nolint:unparam +// return &PubSubPublishEvent{ +// PubSubPublish: *e, +// Duration: time.Since(e.Time), +// }, nil +// } + +// type eventPubSubPublishJSON struct { +// DurationMS int64 `json:"duration_ms"` +// Topic string `json:"topic"` +// Request json.RawMessage `json:"request"` +// Error optional.Option[string] `json:"error,omitempty"` +// } + +// func (s *Service) insertPubSubPublishEvent(ctx context.Context, querier sql.Querier, event *PubSubPublishEvent) error { +// pubsubJSON := eventPubSubPublishJSON{ +// DurationMS: event.Duration.Milliseconds(), +// Topic: event.Topic, +// Request: event.Request, +// Error: event.Error, +// } + +// data, err := json.Marshal(pubsubJSON) +// if err != nil { +// return fmt.Errorf("failed to marshal pubsub event: %w", err) +// } + +// var payload ftlencryption.EncryptedTimelineColumn +// err = s.encryption.EncryptJSON(json.RawMessage(data), &payload) +// if err != nil { +// return fmt.Errorf("failed to encrypt cron JSON: %w", err) +// } + +// err = libdal.TranslatePGError(querier.InsertTimelinePubsubPublishEvent(ctx, sql.InsertTimelinePubsubPublishEventParams{ +// DeploymentKey: event.DeploymentKey, +// RequestKey: event.RequestKey, +// TimeStamp: event.Time, +// SourceModule: event.SourceVerb.Module, +// SourceVerb: event.SourceVerb.Name, +// Topic: event.Topic, +// Payload: payload, +// })) +// if err != nil { +// return fmt.Errorf("failed to insert pubsub publish event: %w", err) +// } +// return err +// } diff --git a/backend/timeline/filters.go b/backend/timeline/filters.go index 718215a02a..c5fff0ec3a 100644 --- a/backend/timeline/filters.go +++ b/backend/timeline/filters.go @@ -191,10 +191,10 @@ func FilterTypes(filters ...*timelinepb.GetTimelineRequest_EventTypeFilter) Time // FilterTimeRange filters events between the given times, inclusive. func FilterTimeRange(filter *timelinepb.GetTimelineRequest_TimeFilter) TimelineFilter { return func(event *timelinepb.Event) bool { - if filter.NewerThan != nil && event.TimeStamp.AsTime().Before(filter.NewerThan.AsTime()) { + if filter.NewerThan != nil && event.Timestamp.AsTime().Before(filter.NewerThan.AsTime()) { return false } - if filter.OlderThan != nil && event.TimeStamp.AsTime().After(filter.OlderThan.AsTime()) { + if filter.OlderThan != nil && event.Timestamp.AsTime().After(filter.OlderThan.AsTime()) { return false } return true diff --git a/backend/timeline/publish.go b/backend/timeline/publish.go new file mode 100644 index 0000000000..22ac3aa5cb --- /dev/null +++ b/backend/timeline/publish.go @@ -0,0 +1,25 @@ +package timeline + +import ( + "context" + + "connectrpc.com/connect" + timelinepb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1" + "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1/timelinev1connect" + "github.com/TBD54566975/ftl/internal/log" + "github.com/TBD54566975/ftl/internal/rpc" +) + +//go:sumtype +type Event interface { + ToReq() *timelinepb.CreateEventRequest + clientEvent() +} + +func Publish(ctx context.Context, event Event) { + client := rpc.ClientFromContext[timelinev1connect.TimelineServiceClient](ctx) + _, err := client.CreateEvent(ctx, connect.NewRequest(event.ToReq())) + if err != nil { + log.FromContext(ctx).Warnf("failed to publish %T event: %v", event, err) + } +} diff --git a/backend/timeline/service.go b/backend/timeline/service.go index 5be3cf4814..918682d4c8 100644 --- a/backend/timeline/service.go +++ b/backend/timeline/service.go @@ -62,7 +62,7 @@ func (s *service) CreateEvent(ctx context.Context, req *connect.Request[timeline event := &timelinepb.Event{ Id: int64(s.nextID), - TimeStamp: timestamppb.Now(), + Timestamp: timestamppb.Now(), } switch entry := req.Msg.Entry.(type) { case *timelinepb.CreateEventRequest_Log: @@ -162,14 +162,19 @@ func (s *service) DeleteOldEvents(ctx context.Context, req *connect.Request[time } filtered := []*timelinepb.Event{} + deleted := int64(0) for _, event := range s.events { _, didNotMatchAFilter := slices.Find(deletionFilters, func(filter TimelineFilter) bool { return !filter(event) }) if didNotMatchAFilter { filtered = append(filtered, event) + } else { + deleted++ } } s.events = filtered - return connect.NewResponse(&timelinepb.DeleteOldEventsResponse{}), nil + return connect.NewResponse(&timelinepb.DeleteOldEventsResponse{ + DeletedCount: deleted, + }), nil } diff --git a/backend/timeline/service_test.go b/backend/timeline/service_test.go index 218c57e2b4..7bafa892e9 100644 --- a/backend/timeline/service_test.go +++ b/backend/timeline/service_test.go @@ -110,10 +110,11 @@ func TestDeleteOldEvents(t *testing.T) { } // Delete half the events (everything older than 3 seconds) - _, err := service.DeleteOldEvents(ctx, connect.NewRequest(&timelinepb.DeleteOldEventsRequest{ + resp, err := service.DeleteOldEvents(ctx, connect.NewRequest(&timelinepb.DeleteOldEventsRequest{ AgeSeconds: 3, EventType: timelinepb.EventType_EVENT_TYPE_UNSPECIFIED, })) assert.NoError(t, err) assert.Equal(t, len(service.events), 150, "expected only half the events to be deleted") + assert.Equal(t, resp.Msg.DeletedCount, 150, "expected half the events to be in the deletion count") } diff --git a/frontend/cli/cmd_serve.go b/frontend/cli/cmd_serve.go index 9b4c12eb9d..807cf83ac5 100644 --- a/frontend/cli/cmd_serve.go +++ b/frontend/cli/cmd_serve.go @@ -28,6 +28,7 @@ import ( "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect" "github.com/TBD54566975/ftl/backend/provisioner" "github.com/TBD54566975/ftl/backend/provisioner/scaling/localscaling" + "github.com/TBD54566975/ftl/backend/timeline" "github.com/TBD54566975/ftl/internal/bind" "github.com/TBD54566975/ftl/internal/configuration" "github.com/TBD54566975/ftl/internal/configuration/manager" @@ -62,6 +63,7 @@ type serveCommonConfig struct { GrafanaImage string `help:"The container image to start for the automatic Grafana instance" default:"grafana/otel-lgtm" env:"FTL_GRAFANA_IMAGE" hidden:""` DisableGrafana bool `help:"Disable the automatic Grafana that is started if no telemetry collector is specified." default:"false"` Ingress ingress.Config `embed:"" prefix:"ingress-"` + Timeline timeline.Config `embed:"" prefix:"timeline-"` Recreate bool `help:"Recreate any stateful resources if they already exist." default:"false"` controller.CommonConfig provisioner.CommonProvisionerConfig @@ -302,6 +304,14 @@ func (s *serveCommonConfig) run( }) } + // Start Timeline + wg.Go(func() error { + err := timeline.Start(ctx, s.Timeline, schemaEventSourceFactory()) + if err != nil { + return fmt.Errorf("cron failed: %w", err) + } + return nil + }) // Start Cron wg.Go(func() error { err := cron.Start(ctx, schemaEventSourceFactory(), verbClient) diff --git a/frontend/cli/main.go b/frontend/cli/main.go index 750d1663d2..14a54a9956 100644 --- a/frontend/cli/main.go +++ b/frontend/cli/main.go @@ -20,6 +20,7 @@ import ( "github.com/TBD54566975/ftl" "github.com/TBD54566975/ftl/backend/controller/admin" provisionerconnect "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/provisioner/v1beta1/provisionerpbconnect" + "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1/timelinev1connect" "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect" "github.com/TBD54566975/ftl/internal" _ "github.com/TBD54566975/ftl/internal/automaxprocs" // Set GOMAXPROCS to match Linux container CPU quota. @@ -231,6 +232,10 @@ func makeBindContext(logger *log.Logger, cancel context.CancelFunc) terminal.Kon ctx = rpc.ContextWithClient(ctx, provisionerServiceClient) kctx.BindTo(provisionerServiceClient, (*provisionerconnect.ProvisionerServiceClient)(nil)) + timelineServiceClient := rpc.Dial(timelinev1connect.NewTimelineServiceClient, cli.TimelineEndpoint.String(), log.Error) + ctx = rpc.ContextWithClient(ctx, timelineServiceClient) + kctx.BindTo(timelineServiceClient, (*timelinev1connect.TimelineServiceClient)(nil)) + err = kctx.BindToProvider(func() (*providers.Registry[configuration.Configuration], error) { return providers.NewDefaultConfigRegistry(), nil }) diff --git a/frontend/console/src/protos/xyz/block/ftl/timeline/v1/event_pb.ts b/frontend/console/src/protos/xyz/block/ftl/timeline/v1/event_pb.ts index 526a2f1d07..92cbaf8dea 100644 --- a/frontend/console/src/protos/xyz/block/ftl/timeline/v1/event_pb.ts +++ b/frontend/console/src/protos/xyz/block/ftl/timeline/v1/event_pb.ts @@ -160,9 +160,9 @@ export class LogEvent extends Message { requestKey?: string; /** - * @generated from field: google.protobuf.Timestamp time_stamp = 3; + * @generated from field: google.protobuf.Timestamp timestamp = 3; */ - timeStamp?: Timestamp; + timestamp?: Timestamp; /** * @generated from field: int32 log_level = 4; @@ -199,7 +199,7 @@ export class LogEvent extends Message { static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 1, name: "deployment_key", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "request_key", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, - { no: 3, name: "time_stamp", kind: "message", T: Timestamp }, + { no: 3, name: "timestamp", kind: "message", T: Timestamp }, { no: 4, name: "log_level", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 5, name: "attributes", kind: "map", K: 9 /* ScalarType.STRING */, V: {kind: "scalar", T: 9 /* ScalarType.STRING */} }, { no: 6, name: "message", kind: "scalar", T: 9 /* ScalarType.STRING */ }, @@ -239,9 +239,9 @@ export class CallEvent extends Message { deploymentKey = ""; /** - * @generated from field: google.protobuf.Timestamp time_stamp = 3; + * @generated from field: google.protobuf.Timestamp timestamp = 3; */ - timeStamp?: Timestamp; + timestamp?: Timestamp; /** * @generated from field: optional xyz.block.ftl.schema.v1.Ref source_verb_ref = 11; @@ -288,7 +288,7 @@ export class CallEvent extends Message { static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 1, name: "request_key", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, { no: 2, name: "deployment_key", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - { no: 3, name: "time_stamp", kind: "message", T: Timestamp }, + { no: 3, name: "timestamp", kind: "message", T: Timestamp }, { no: 11, name: "source_verb_ref", kind: "message", T: Ref, opt: true }, { no: 12, name: "destination_verb_ref", kind: "message", T: Ref }, { no: 6, name: "duration", kind: "message", T: Duration }, @@ -460,9 +460,9 @@ export class IngressEvent extends Message { statusCode = 0; /** - * @generated from field: google.protobuf.Timestamp time_stamp = 8; + * @generated from field: google.protobuf.Timestamp timestamp = 8; */ - timeStamp?: Timestamp; + timestamp?: Timestamp; /** * @generated from field: google.protobuf.Duration duration = 9; @@ -508,7 +508,7 @@ export class IngressEvent extends Message { { no: 4, name: "method", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "path", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 7, name: "status_code", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, - { no: 8, name: "time_stamp", kind: "message", T: Timestamp }, + { no: 8, name: "timestamp", kind: "message", T: Timestamp }, { no: 9, name: "duration", kind: "message", T: Duration }, { no: 10, name: "request", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 11, name: "request_header", kind: "scalar", T: 9 /* ScalarType.STRING */ }, @@ -549,9 +549,9 @@ export class CronScheduledEvent extends Message { verbRef?: Ref; /** - * @generated from field: google.protobuf.Timestamp time_stamp = 3; + * @generated from field: google.protobuf.Timestamp timestamp = 3; */ - timeStamp?: Timestamp; + timestamp?: Timestamp; /** * @generated from field: google.protobuf.Duration duration = 4; @@ -583,7 +583,7 @@ export class CronScheduledEvent extends Message { static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 1, name: "deployment_key", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "verb_ref", kind: "message", T: Ref }, - { no: 3, name: "time_stamp", kind: "message", T: Timestamp }, + { no: 3, name: "timestamp", kind: "message", T: Timestamp }, { no: 4, name: "duration", kind: "message", T: Duration }, { no: 5, name: "scheduled_at", kind: "message", T: Timestamp }, { no: 6, name: "schedule", kind: "scalar", T: 9 /* ScalarType.STRING */ }, @@ -627,9 +627,9 @@ export class AsyncExecuteEvent extends Message { verbRef?: Ref; /** - * @generated from field: google.protobuf.Timestamp time_stamp = 4; + * @generated from field: google.protobuf.Timestamp timestamp = 4; */ - timeStamp?: Timestamp; + timestamp?: Timestamp; /** * @generated from field: google.protobuf.Duration duration = 5; @@ -657,7 +657,7 @@ export class AsyncExecuteEvent extends Message { { no: 1, name: "deployment_key", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "request_key", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, { no: 3, name: "verb_ref", kind: "message", T: Ref }, - { no: 4, name: "time_stamp", kind: "message", T: Timestamp }, + { no: 4, name: "timestamp", kind: "message", T: Timestamp }, { no: 5, name: "duration", kind: "message", T: Duration }, { no: 6, name: "async_event_type", kind: "enum", T: proto3.getEnumType(AsyncExecuteEventType) }, { no: 7, name: "error", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, @@ -700,9 +700,9 @@ export class PubSubPublishEvent extends Message { verbRef?: Ref; /** - * @generated from field: google.protobuf.Timestamp time_stamp = 4; + * @generated from field: google.protobuf.Timestamp timestamp = 4; */ - timeStamp?: Timestamp; + timestamp?: Timestamp; /** * @generated from field: google.protobuf.Duration duration = 5; @@ -735,7 +735,7 @@ export class PubSubPublishEvent extends Message { { no: 1, name: "deployment_key", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "request_key", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, { no: 3, name: "verb_ref", kind: "message", T: Ref }, - { no: 4, name: "time_stamp", kind: "message", T: Timestamp }, + { no: 4, name: "timestamp", kind: "message", T: Timestamp }, { no: 5, name: "duration", kind: "message", T: Duration }, { no: 6, name: "topic", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 7, name: "request", kind: "scalar", T: 9 /* ScalarType.STRING */ }, @@ -784,9 +784,9 @@ export class PubSubConsumeEvent extends Message { destVerbName?: string; /** - * @generated from field: google.protobuf.Timestamp time_stamp = 5; + * @generated from field: google.protobuf.Timestamp timestamp = 5; */ - timeStamp?: Timestamp; + timestamp?: Timestamp; /** * @generated from field: google.protobuf.Duration duration = 6; @@ -815,7 +815,7 @@ export class PubSubConsumeEvent extends Message { { no: 2, name: "request_key", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, { no: 3, name: "dest_verb_module", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, { no: 4, name: "dest_verb_name", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, - { no: 5, name: "time_stamp", kind: "message", T: Timestamp }, + { no: 5, name: "timestamp", kind: "message", T: Timestamp }, { no: 6, name: "duration", kind: "message", T: Duration }, { no: 7, name: "topic", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 8, name: "error", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, @@ -843,9 +843,9 @@ export class PubSubConsumeEvent extends Message { */ export class Event extends Message { /** - * @generated from field: google.protobuf.Timestamp time_stamp = 1; + * @generated from field: google.protobuf.Timestamp timestamp = 1; */ - timeStamp?: Timestamp; + timestamp?: Timestamp; /** * Unique ID for event. @@ -921,7 +921,7 @@ export class Event extends Message { static readonly runtime: typeof proto3 = proto3; static readonly typeName = "xyz.block.ftl.timeline.v1.Event"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: "time_stamp", kind: "message", T: Timestamp }, + { no: 1, name: "timestamp", kind: "message", T: Timestamp }, { no: 2, name: "id", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 3, name: "log", kind: "message", T: LogEvent, oneof: "entry" }, { no: 4, name: "call", kind: "message", T: CallEvent, oneof: "entry" }, diff --git a/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/timeline/v1/event_pb2.py b/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/timeline/v1/event_pb2.py index 61fdf511f7..498ce32a32 100644 --- a/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/timeline/v1/event_pb2.py +++ b/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/timeline/v1/event_pb2.py @@ -27,7 +27,7 @@ from xyz.block.ftl.schema.v1 import schema_pb2 as xyz_dot_block_dot_ftl_dot_schema_dot_v1_dot_schema__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%xyz/block/ftl/timeline/v1/event.proto\x12\x19xyz.block.ftl.timeline.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a$xyz/block/ftl/schema/v1/schema.proto\"\xb7\x03\n\x08LogEvent\x12%\n\x0e\x64\x65ployment_key\x18\x01 \x01(\tR\rdeploymentKey\x12$\n\x0brequest_key\x18\x02 \x01(\tH\x00R\nrequestKey\x88\x01\x01\x12\x39\n\ntime_stamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimeStamp\x12\x1b\n\tlog_level\x18\x04 \x01(\x05R\x08logLevel\x12S\n\nattributes\x18\x05 \x03(\x0b\x32\x33.xyz.block.ftl.timeline.v1.LogEvent.AttributesEntryR\nattributes\x12\x18\n\x07message\x18\x06 \x01(\tR\x07message\x12\x19\n\x05\x65rror\x18\x07 \x01(\tH\x01R\x05\x65rror\x88\x01\x01\x12\x19\n\x05stack\x18\x08 \x01(\tH\x02R\x05stack\x88\x01\x01\x1a=\n\x0f\x41ttributesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\x42\x0e\n\x0c_request_keyB\x08\n\x06_errorB\x08\n\x06_stack\"\x95\x04\n\tCallEvent\x12$\n\x0brequest_key\x18\x01 \x01(\tH\x00R\nrequestKey\x88\x01\x01\x12%\n\x0e\x64\x65ployment_key\x18\x02 \x01(\tR\rdeploymentKey\x12\x39\n\ntime_stamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimeStamp\x12I\n\x0fsource_verb_ref\x18\x0b \x01(\x0b\x32\x1c.xyz.block.ftl.schema.v1.RefH\x01R\rsourceVerbRef\x88\x01\x01\x12N\n\x14\x64\x65stination_verb_ref\x18\x0c \x01(\x0b\x32\x1c.xyz.block.ftl.schema.v1.RefR\x12\x64\x65stinationVerbRef\x12\x35\n\x08\x64uration\x18\x06 \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12\x18\n\x07request\x18\x07 \x01(\tR\x07request\x12\x1a\n\x08response\x18\x08 \x01(\tR\x08response\x12\x19\n\x05\x65rror\x18\t \x01(\tH\x02R\x05\x65rror\x88\x01\x01\x12\x19\n\x05stack\x18\n \x01(\tH\x03R\x05stack\x88\x01\x01\x42\x0e\n\x0c_request_keyB\x12\n\x10_source_verb_refB\x08\n\x06_errorB\x08\n\x06_stackJ\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06\"\xb8\x01\n\x16\x44\x65ploymentCreatedEvent\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x1a\n\x08language\x18\x02 \x01(\tR\x08language\x12\x1f\n\x0bmodule_name\x18\x03 \x01(\tR\nmoduleName\x12!\n\x0cmin_replicas\x18\x04 \x01(\x05R\x0bminReplicas\x12\x1f\n\x08replaced\x18\x05 \x01(\tH\x00R\x08replaced\x88\x01\x01\x42\x0b\n\t_replaced\"y\n\x16\x44\x65ploymentUpdatedEvent\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12!\n\x0cmin_replicas\x18\x02 \x01(\x05R\x0bminReplicas\x12*\n\x11prev_min_replicas\x18\x03 \x01(\x05R\x0fprevMinReplicas\"\x8e\x04\n\x0cIngressEvent\x12%\n\x0e\x64\x65ployment_key\x18\x01 \x01(\tR\rdeploymentKey\x12$\n\x0brequest_key\x18\x02 \x01(\tH\x00R\nrequestKey\x88\x01\x01\x12\x37\n\x08verb_ref\x18\x03 \x01(\x0b\x32\x1c.xyz.block.ftl.schema.v1.RefR\x07verbRef\x12\x16\n\x06method\x18\x04 \x01(\tR\x06method\x12\x12\n\x04path\x18\x05 \x01(\tR\x04path\x12\x1f\n\x0bstatus_code\x18\x07 \x01(\x05R\nstatusCode\x12\x39\n\ntime_stamp\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimeStamp\x12\x35\n\x08\x64uration\x18\t \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12\x18\n\x07request\x18\n \x01(\tR\x07request\x12%\n\x0erequest_header\x18\x0b \x01(\tR\rrequestHeader\x12\x1a\n\x08response\x18\x0c \x01(\tR\x08response\x12\'\n\x0fresponse_header\x18\r \x01(\tR\x0eresponseHeader\x12\x19\n\x05\x65rror\x18\x0e \x01(\tH\x01R\x05\x65rror\x88\x01\x01\x42\x0e\n\x0c_request_keyB\x08\n\x06_error\"\xe6\x02\n\x12\x43ronScheduledEvent\x12%\n\x0e\x64\x65ployment_key\x18\x01 \x01(\tR\rdeploymentKey\x12\x37\n\x08verb_ref\x18\x02 \x01(\x0b\x32\x1c.xyz.block.ftl.schema.v1.RefR\x07verbRef\x12\x39\n\ntime_stamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimeStamp\x12\x35\n\x08\x64uration\x18\x04 \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12=\n\x0cscheduled_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0bscheduledAt\x12\x1a\n\x08schedule\x18\x06 \x01(\tR\x08schedule\x12\x19\n\x05\x65rror\x18\x07 \x01(\tH\x00R\x05\x65rror\x88\x01\x01\x42\x08\n\x06_error\"\x9c\x03\n\x11\x41syncExecuteEvent\x12%\n\x0e\x64\x65ployment_key\x18\x01 \x01(\tR\rdeploymentKey\x12$\n\x0brequest_key\x18\x02 \x01(\tH\x00R\nrequestKey\x88\x01\x01\x12\x37\n\x08verb_ref\x18\x03 \x01(\x0b\x32\x1c.xyz.block.ftl.schema.v1.RefR\x07verbRef\x12\x39\n\ntime_stamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimeStamp\x12\x35\n\x08\x64uration\x18\x05 \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12Z\n\x10\x61sync_event_type\x18\x06 \x01(\x0e\x32\x30.xyz.block.ftl.timeline.v1.AsyncExecuteEventTypeR\x0e\x61syncEventType\x12\x19\n\x05\x65rror\x18\x07 \x01(\tH\x01R\x05\x65rror\x88\x01\x01\x42\x0e\n\x0c_request_keyB\x08\n\x06_error\"\xf1\x02\n\x12PubSubPublishEvent\x12%\n\x0e\x64\x65ployment_key\x18\x01 \x01(\tR\rdeploymentKey\x12$\n\x0brequest_key\x18\x02 \x01(\tH\x00R\nrequestKey\x88\x01\x01\x12\x37\n\x08verb_ref\x18\x03 \x01(\x0b\x32\x1c.xyz.block.ftl.schema.v1.RefR\x07verbRef\x12\x39\n\ntime_stamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimeStamp\x12\x35\n\x08\x64uration\x18\x05 \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12\x14\n\x05topic\x18\x06 \x01(\tR\x05topic\x12\x18\n\x07request\x18\x07 \x01(\tR\x07request\x12\x19\n\x05\x65rror\x18\x08 \x01(\tH\x01R\x05\x65rror\x88\x01\x01\x42\x0e\n\x0c_request_keyB\x08\n\x06_error\"\xa0\x03\n\x12PubSubConsumeEvent\x12%\n\x0e\x64\x65ployment_key\x18\x01 \x01(\tR\rdeploymentKey\x12$\n\x0brequest_key\x18\x02 \x01(\tH\x00R\nrequestKey\x88\x01\x01\x12-\n\x10\x64\x65st_verb_module\x18\x03 \x01(\tH\x01R\x0e\x64\x65stVerbModule\x88\x01\x01\x12)\n\x0e\x64\x65st_verb_name\x18\x04 \x01(\tH\x02R\x0c\x64\x65stVerbName\x88\x01\x01\x12\x39\n\ntime_stamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimeStamp\x12\x35\n\x08\x64uration\x18\x06 \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12\x14\n\x05topic\x18\x07 \x01(\tR\x05topic\x12\x19\n\x05\x65rror\x18\x08 \x01(\tH\x03R\x05\x65rror\x88\x01\x01\x42\x0e\n\x0c_request_keyB\x13\n\x11_dest_verb_moduleB\x11\n\x0f_dest_verb_nameB\x08\n\x06_error\"\xba\x06\n\x05\x45vent\x12\x39\n\ntime_stamp\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimeStamp\x12\x0e\n\x02id\x18\x02 \x01(\x03R\x02id\x12\x37\n\x03log\x18\x03 \x01(\x0b\x32#.xyz.block.ftl.timeline.v1.LogEventH\x00R\x03log\x12:\n\x04\x63\x61ll\x18\x04 \x01(\x0b\x32$.xyz.block.ftl.timeline.v1.CallEventH\x00R\x04\x63\x61ll\x12\x62\n\x12\x64\x65ployment_created\x18\x05 \x01(\x0b\x32\x31.xyz.block.ftl.timeline.v1.DeploymentCreatedEventH\x00R\x11\x64\x65ploymentCreated\x12\x62\n\x12\x64\x65ployment_updated\x18\x06 \x01(\x0b\x32\x31.xyz.block.ftl.timeline.v1.DeploymentUpdatedEventH\x00R\x11\x64\x65ploymentUpdated\x12\x43\n\x07ingress\x18\x07 \x01(\x0b\x32\'.xyz.block.ftl.timeline.v1.IngressEventH\x00R\x07ingress\x12V\n\x0e\x63ron_scheduled\x18\x08 \x01(\x0b\x32-.xyz.block.ftl.timeline.v1.CronScheduledEventH\x00R\rcronScheduled\x12S\n\rasync_execute\x18\t \x01(\x0b\x32,.xyz.block.ftl.timeline.v1.AsyncExecuteEventH\x00R\x0c\x61syncExecute\x12V\n\x0epubsub_publish\x18\n \x01(\x0b\x32-.xyz.block.ftl.timeline.v1.PubSubPublishEventH\x00R\rpubsubPublish\x12V\n\x0epubsub_consume\x18\x0b \x01(\x0b\x32-.xyz.block.ftl.timeline.v1.PubSubConsumeEventH\x00R\rpubsubConsumeB\x07\n\x05\x65ntry*\xa9\x02\n\tEventType\x12\x1a\n\x16\x45VENT_TYPE_UNSPECIFIED\x10\x00\x12\x12\n\x0e\x45VENT_TYPE_LOG\x10\x01\x12\x13\n\x0f\x45VENT_TYPE_CALL\x10\x02\x12!\n\x1d\x45VENT_TYPE_DEPLOYMENT_CREATED\x10\x03\x12!\n\x1d\x45VENT_TYPE_DEPLOYMENT_UPDATED\x10\x04\x12\x16\n\x12\x45VENT_TYPE_INGRESS\x10\x05\x12\x1d\n\x19\x45VENT_TYPE_CRON_SCHEDULED\x10\x06\x12\x1c\n\x18\x45VENT_TYPE_ASYNC_EXECUTE\x10\x07\x12\x1d\n\x19\x45VENT_TYPE_PUBSUB_PUBLISH\x10\x08\x12\x1d\n\x19\x45VENT_TYPE_PUBSUB_CONSUME\x10\t*\x89\x01\n\x15\x41syncExecuteEventType\x12(\n$ASYNC_EXECUTE_EVENT_TYPE_UNSPECIFIED\x10\x00\x12!\n\x1d\x41SYNC_EXECUTE_EVENT_TYPE_CRON\x10\x01\x12#\n\x1f\x41SYNC_EXECUTE_EVENT_TYPE_PUBSUB\x10\x02*\x8c\x01\n\x08LogLevel\x12\x19\n\x15LOG_LEVEL_UNSPECIFIED\x10\x00\x12\x13\n\x0fLOG_LEVEL_TRACE\x10\x01\x12\x13\n\x0fLOG_LEVEL_DEBUG\x10\x05\x12\x12\n\x0eLOG_LEVEL_INFO\x10\t\x12\x12\n\x0eLOG_LEVEL_WARN\x10\r\x12\x13\n\x0fLOG_LEVEL_ERROR\x10\x11\x42RP\x01ZNgithub.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1;timelinev1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%xyz/block/ftl/timeline/v1/event.proto\x12\x19xyz.block.ftl.timeline.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a$xyz/block/ftl/schema/v1/schema.proto\"\xb6\x03\n\x08LogEvent\x12%\n\x0e\x64\x65ployment_key\x18\x01 \x01(\tR\rdeploymentKey\x12$\n\x0brequest_key\x18\x02 \x01(\tH\x00R\nrequestKey\x88\x01\x01\x12\x38\n\ttimestamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimestamp\x12\x1b\n\tlog_level\x18\x04 \x01(\x05R\x08logLevel\x12S\n\nattributes\x18\x05 \x03(\x0b\x32\x33.xyz.block.ftl.timeline.v1.LogEvent.AttributesEntryR\nattributes\x12\x18\n\x07message\x18\x06 \x01(\tR\x07message\x12\x19\n\x05\x65rror\x18\x07 \x01(\tH\x01R\x05\x65rror\x88\x01\x01\x12\x19\n\x05stack\x18\x08 \x01(\tH\x02R\x05stack\x88\x01\x01\x1a=\n\x0f\x41ttributesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\x42\x0e\n\x0c_request_keyB\x08\n\x06_errorB\x08\n\x06_stack\"\x94\x04\n\tCallEvent\x12$\n\x0brequest_key\x18\x01 \x01(\tH\x00R\nrequestKey\x88\x01\x01\x12%\n\x0e\x64\x65ployment_key\x18\x02 \x01(\tR\rdeploymentKey\x12\x38\n\ttimestamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimestamp\x12I\n\x0fsource_verb_ref\x18\x0b \x01(\x0b\x32\x1c.xyz.block.ftl.schema.v1.RefH\x01R\rsourceVerbRef\x88\x01\x01\x12N\n\x14\x64\x65stination_verb_ref\x18\x0c \x01(\x0b\x32\x1c.xyz.block.ftl.schema.v1.RefR\x12\x64\x65stinationVerbRef\x12\x35\n\x08\x64uration\x18\x06 \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12\x18\n\x07request\x18\x07 \x01(\tR\x07request\x12\x1a\n\x08response\x18\x08 \x01(\tR\x08response\x12\x19\n\x05\x65rror\x18\t \x01(\tH\x02R\x05\x65rror\x88\x01\x01\x12\x19\n\x05stack\x18\n \x01(\tH\x03R\x05stack\x88\x01\x01\x42\x0e\n\x0c_request_keyB\x12\n\x10_source_verb_refB\x08\n\x06_errorB\x08\n\x06_stackJ\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06\"\xb8\x01\n\x16\x44\x65ploymentCreatedEvent\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x1a\n\x08language\x18\x02 \x01(\tR\x08language\x12\x1f\n\x0bmodule_name\x18\x03 \x01(\tR\nmoduleName\x12!\n\x0cmin_replicas\x18\x04 \x01(\x05R\x0bminReplicas\x12\x1f\n\x08replaced\x18\x05 \x01(\tH\x00R\x08replaced\x88\x01\x01\x42\x0b\n\t_replaced\"y\n\x16\x44\x65ploymentUpdatedEvent\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12!\n\x0cmin_replicas\x18\x02 \x01(\x05R\x0bminReplicas\x12*\n\x11prev_min_replicas\x18\x03 \x01(\x05R\x0fprevMinReplicas\"\x8d\x04\n\x0cIngressEvent\x12%\n\x0e\x64\x65ployment_key\x18\x01 \x01(\tR\rdeploymentKey\x12$\n\x0brequest_key\x18\x02 \x01(\tH\x00R\nrequestKey\x88\x01\x01\x12\x37\n\x08verb_ref\x18\x03 \x01(\x0b\x32\x1c.xyz.block.ftl.schema.v1.RefR\x07verbRef\x12\x16\n\x06method\x18\x04 \x01(\tR\x06method\x12\x12\n\x04path\x18\x05 \x01(\tR\x04path\x12\x1f\n\x0bstatus_code\x18\x07 \x01(\x05R\nstatusCode\x12\x38\n\ttimestamp\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimestamp\x12\x35\n\x08\x64uration\x18\t \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12\x18\n\x07request\x18\n \x01(\tR\x07request\x12%\n\x0erequest_header\x18\x0b \x01(\tR\rrequestHeader\x12\x1a\n\x08response\x18\x0c \x01(\tR\x08response\x12\'\n\x0fresponse_header\x18\r \x01(\tR\x0eresponseHeader\x12\x19\n\x05\x65rror\x18\x0e \x01(\tH\x01R\x05\x65rror\x88\x01\x01\x42\x0e\n\x0c_request_keyB\x08\n\x06_error\"\xe5\x02\n\x12\x43ronScheduledEvent\x12%\n\x0e\x64\x65ployment_key\x18\x01 \x01(\tR\rdeploymentKey\x12\x37\n\x08verb_ref\x18\x02 \x01(\x0b\x32\x1c.xyz.block.ftl.schema.v1.RefR\x07verbRef\x12\x38\n\ttimestamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimestamp\x12\x35\n\x08\x64uration\x18\x04 \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12=\n\x0cscheduled_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0bscheduledAt\x12\x1a\n\x08schedule\x18\x06 \x01(\tR\x08schedule\x12\x19\n\x05\x65rror\x18\x07 \x01(\tH\x00R\x05\x65rror\x88\x01\x01\x42\x08\n\x06_error\"\x9b\x03\n\x11\x41syncExecuteEvent\x12%\n\x0e\x64\x65ployment_key\x18\x01 \x01(\tR\rdeploymentKey\x12$\n\x0brequest_key\x18\x02 \x01(\tH\x00R\nrequestKey\x88\x01\x01\x12\x37\n\x08verb_ref\x18\x03 \x01(\x0b\x32\x1c.xyz.block.ftl.schema.v1.RefR\x07verbRef\x12\x38\n\ttimestamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimestamp\x12\x35\n\x08\x64uration\x18\x05 \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12Z\n\x10\x61sync_event_type\x18\x06 \x01(\x0e\x32\x30.xyz.block.ftl.timeline.v1.AsyncExecuteEventTypeR\x0e\x61syncEventType\x12\x19\n\x05\x65rror\x18\x07 \x01(\tH\x01R\x05\x65rror\x88\x01\x01\x42\x0e\n\x0c_request_keyB\x08\n\x06_error\"\xf0\x02\n\x12PubSubPublishEvent\x12%\n\x0e\x64\x65ployment_key\x18\x01 \x01(\tR\rdeploymentKey\x12$\n\x0brequest_key\x18\x02 \x01(\tH\x00R\nrequestKey\x88\x01\x01\x12\x37\n\x08verb_ref\x18\x03 \x01(\x0b\x32\x1c.xyz.block.ftl.schema.v1.RefR\x07verbRef\x12\x38\n\ttimestamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimestamp\x12\x35\n\x08\x64uration\x18\x05 \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12\x14\n\x05topic\x18\x06 \x01(\tR\x05topic\x12\x18\n\x07request\x18\x07 \x01(\tR\x07request\x12\x19\n\x05\x65rror\x18\x08 \x01(\tH\x01R\x05\x65rror\x88\x01\x01\x42\x0e\n\x0c_request_keyB\x08\n\x06_error\"\x9f\x03\n\x12PubSubConsumeEvent\x12%\n\x0e\x64\x65ployment_key\x18\x01 \x01(\tR\rdeploymentKey\x12$\n\x0brequest_key\x18\x02 \x01(\tH\x00R\nrequestKey\x88\x01\x01\x12-\n\x10\x64\x65st_verb_module\x18\x03 \x01(\tH\x01R\x0e\x64\x65stVerbModule\x88\x01\x01\x12)\n\x0e\x64\x65st_verb_name\x18\x04 \x01(\tH\x02R\x0c\x64\x65stVerbName\x88\x01\x01\x12\x38\n\ttimestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimestamp\x12\x35\n\x08\x64uration\x18\x06 \x01(\x0b\x32\x19.google.protobuf.DurationR\x08\x64uration\x12\x14\n\x05topic\x18\x07 \x01(\tR\x05topic\x12\x19\n\x05\x65rror\x18\x08 \x01(\tH\x03R\x05\x65rror\x88\x01\x01\x42\x0e\n\x0c_request_keyB\x13\n\x11_dest_verb_moduleB\x11\n\x0f_dest_verb_nameB\x08\n\x06_error\"\xb9\x06\n\x05\x45vent\x12\x38\n\ttimestamp\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimestamp\x12\x0e\n\x02id\x18\x02 \x01(\x03R\x02id\x12\x37\n\x03log\x18\x03 \x01(\x0b\x32#.xyz.block.ftl.timeline.v1.LogEventH\x00R\x03log\x12:\n\x04\x63\x61ll\x18\x04 \x01(\x0b\x32$.xyz.block.ftl.timeline.v1.CallEventH\x00R\x04\x63\x61ll\x12\x62\n\x12\x64\x65ployment_created\x18\x05 \x01(\x0b\x32\x31.xyz.block.ftl.timeline.v1.DeploymentCreatedEventH\x00R\x11\x64\x65ploymentCreated\x12\x62\n\x12\x64\x65ployment_updated\x18\x06 \x01(\x0b\x32\x31.xyz.block.ftl.timeline.v1.DeploymentUpdatedEventH\x00R\x11\x64\x65ploymentUpdated\x12\x43\n\x07ingress\x18\x07 \x01(\x0b\x32\'.xyz.block.ftl.timeline.v1.IngressEventH\x00R\x07ingress\x12V\n\x0e\x63ron_scheduled\x18\x08 \x01(\x0b\x32-.xyz.block.ftl.timeline.v1.CronScheduledEventH\x00R\rcronScheduled\x12S\n\rasync_execute\x18\t \x01(\x0b\x32,.xyz.block.ftl.timeline.v1.AsyncExecuteEventH\x00R\x0c\x61syncExecute\x12V\n\x0epubsub_publish\x18\n \x01(\x0b\x32-.xyz.block.ftl.timeline.v1.PubSubPublishEventH\x00R\rpubsubPublish\x12V\n\x0epubsub_consume\x18\x0b \x01(\x0b\x32-.xyz.block.ftl.timeline.v1.PubSubConsumeEventH\x00R\rpubsubConsumeB\x07\n\x05\x65ntry*\xa9\x02\n\tEventType\x12\x1a\n\x16\x45VENT_TYPE_UNSPECIFIED\x10\x00\x12\x12\n\x0e\x45VENT_TYPE_LOG\x10\x01\x12\x13\n\x0f\x45VENT_TYPE_CALL\x10\x02\x12!\n\x1d\x45VENT_TYPE_DEPLOYMENT_CREATED\x10\x03\x12!\n\x1d\x45VENT_TYPE_DEPLOYMENT_UPDATED\x10\x04\x12\x16\n\x12\x45VENT_TYPE_INGRESS\x10\x05\x12\x1d\n\x19\x45VENT_TYPE_CRON_SCHEDULED\x10\x06\x12\x1c\n\x18\x45VENT_TYPE_ASYNC_EXECUTE\x10\x07\x12\x1d\n\x19\x45VENT_TYPE_PUBSUB_PUBLISH\x10\x08\x12\x1d\n\x19\x45VENT_TYPE_PUBSUB_CONSUME\x10\t*\x89\x01\n\x15\x41syncExecuteEventType\x12(\n$ASYNC_EXECUTE_EVENT_TYPE_UNSPECIFIED\x10\x00\x12!\n\x1d\x41SYNC_EXECUTE_EVENT_TYPE_CRON\x10\x01\x12#\n\x1f\x41SYNC_EXECUTE_EVENT_TYPE_PUBSUB\x10\x02*\x8c\x01\n\x08LogLevel\x12\x19\n\x15LOG_LEVEL_UNSPECIFIED\x10\x00\x12\x13\n\x0fLOG_LEVEL_TRACE\x10\x01\x12\x13\n\x0fLOG_LEVEL_DEBUG\x10\x05\x12\x12\n\x0eLOG_LEVEL_INFO\x10\t\x12\x12\n\x0eLOG_LEVEL_WARN\x10\r\x12\x13\n\x0fLOG_LEVEL_ERROR\x10\x11\x42RP\x01ZNgithub.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1;timelinev1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -37,32 +37,32 @@ _globals['DESCRIPTOR']._serialized_options = b'P\001ZNgithub.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/timeline/v1;timelinev1' _globals['_LOGEVENT_ATTRIBUTESENTRY']._loaded_options = None _globals['_LOGEVENT_ATTRIBUTESENTRY']._serialized_options = b'8\001' - _globals['_EVENTTYPE']._serialized_start=4385 - _globals['_EVENTTYPE']._serialized_end=4682 - _globals['_ASYNCEXECUTEEVENTTYPE']._serialized_start=4685 - _globals['_ASYNCEXECUTEEVENTTYPE']._serialized_end=4822 - _globals['_LOGLEVEL']._serialized_start=4825 - _globals['_LOGLEVEL']._serialized_end=4965 + _globals['_EVENTTYPE']._serialized_start=4377 + _globals['_EVENTTYPE']._serialized_end=4674 + _globals['_ASYNCEXECUTEEVENTTYPE']._serialized_start=4677 + _globals['_ASYNCEXECUTEEVENTTYPE']._serialized_end=4814 + _globals['_LOGLEVEL']._serialized_start=4817 + _globals['_LOGLEVEL']._serialized_end=4957 _globals['_LOGEVENT']._serialized_start=172 - _globals['_LOGEVENT']._serialized_end=611 - _globals['_LOGEVENT_ATTRIBUTESENTRY']._serialized_start=514 - _globals['_LOGEVENT_ATTRIBUTESENTRY']._serialized_end=575 - _globals['_CALLEVENT']._serialized_start=614 - _globals['_CALLEVENT']._serialized_end=1147 - _globals['_DEPLOYMENTCREATEDEVENT']._serialized_start=1150 - _globals['_DEPLOYMENTCREATEDEVENT']._serialized_end=1334 - _globals['_DEPLOYMENTUPDATEDEVENT']._serialized_start=1336 - _globals['_DEPLOYMENTUPDATEDEVENT']._serialized_end=1457 - _globals['_INGRESSEVENT']._serialized_start=1460 - _globals['_INGRESSEVENT']._serialized_end=1986 - _globals['_CRONSCHEDULEDEVENT']._serialized_start=1989 - _globals['_CRONSCHEDULEDEVENT']._serialized_end=2347 - _globals['_ASYNCEXECUTEEVENT']._serialized_start=2350 - _globals['_ASYNCEXECUTEEVENT']._serialized_end=2762 - _globals['_PUBSUBPUBLISHEVENT']._serialized_start=2765 - _globals['_PUBSUBPUBLISHEVENT']._serialized_end=3134 - _globals['_PUBSUBCONSUMEEVENT']._serialized_start=3137 - _globals['_PUBSUBCONSUMEEVENT']._serialized_end=3553 - _globals['_EVENT']._serialized_start=3556 - _globals['_EVENT']._serialized_end=4382 + _globals['_LOGEVENT']._serialized_end=610 + _globals['_LOGEVENT_ATTRIBUTESENTRY']._serialized_start=513 + _globals['_LOGEVENT_ATTRIBUTESENTRY']._serialized_end=574 + _globals['_CALLEVENT']._serialized_start=613 + _globals['_CALLEVENT']._serialized_end=1145 + _globals['_DEPLOYMENTCREATEDEVENT']._serialized_start=1148 + _globals['_DEPLOYMENTCREATEDEVENT']._serialized_end=1332 + _globals['_DEPLOYMENTUPDATEDEVENT']._serialized_start=1334 + _globals['_DEPLOYMENTUPDATEDEVENT']._serialized_end=1455 + _globals['_INGRESSEVENT']._serialized_start=1458 + _globals['_INGRESSEVENT']._serialized_end=1983 + _globals['_CRONSCHEDULEDEVENT']._serialized_start=1986 + _globals['_CRONSCHEDULEDEVENT']._serialized_end=2343 + _globals['_ASYNCEXECUTEEVENT']._serialized_start=2346 + _globals['_ASYNCEXECUTEEVENT']._serialized_end=2757 + _globals['_PUBSUBPUBLISHEVENT']._serialized_start=2760 + _globals['_PUBSUBPUBLISHEVENT']._serialized_end=3128 + _globals['_PUBSUBCONSUMEEVENT']._serialized_start=3131 + _globals['_PUBSUBCONSUMEEVENT']._serialized_end=3546 + _globals['_EVENT']._serialized_start=3549 + _globals['_EVENT']._serialized_end=4374 # @@protoc_insertion_point(module_scope) diff --git a/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/timeline/v1/event_pb2.pyi b/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/timeline/v1/event_pb2.pyi index b87ae577f4..12d60460bc 100644 --- a/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/timeline/v1/event_pb2.pyi +++ b/python-runtime/ftl/src/ftl/protos/xyz/block/ftl/timeline/v1/event_pb2.pyi @@ -57,7 +57,7 @@ LOG_LEVEL_WARN: LogLevel LOG_LEVEL_ERROR: LogLevel class LogEvent(_message.Message): - __slots__ = ("deployment_key", "request_key", "time_stamp", "log_level", "attributes", "message", "error", "stack") + __slots__ = ("deployment_key", "request_key", "timestamp", "log_level", "attributes", "message", "error", "stack") class AttributesEntry(_message.Message): __slots__ = ("key", "value") KEY_FIELD_NUMBER: _ClassVar[int] @@ -67,7 +67,7 @@ class LogEvent(_message.Message): def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... DEPLOYMENT_KEY_FIELD_NUMBER: _ClassVar[int] REQUEST_KEY_FIELD_NUMBER: _ClassVar[int] - TIME_STAMP_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] LOG_LEVEL_FIELD_NUMBER: _ClassVar[int] ATTRIBUTES_FIELD_NUMBER: _ClassVar[int] MESSAGE_FIELD_NUMBER: _ClassVar[int] @@ -75,19 +75,19 @@ class LogEvent(_message.Message): STACK_FIELD_NUMBER: _ClassVar[int] deployment_key: str request_key: str - time_stamp: _timestamp_pb2.Timestamp + timestamp: _timestamp_pb2.Timestamp log_level: int attributes: _containers.ScalarMap[str, str] message: str error: str stack: str - def __init__(self, deployment_key: _Optional[str] = ..., request_key: _Optional[str] = ..., time_stamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., log_level: _Optional[int] = ..., attributes: _Optional[_Mapping[str, str]] = ..., message: _Optional[str] = ..., error: _Optional[str] = ..., stack: _Optional[str] = ...) -> None: ... + def __init__(self, deployment_key: _Optional[str] = ..., request_key: _Optional[str] = ..., timestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., log_level: _Optional[int] = ..., attributes: _Optional[_Mapping[str, str]] = ..., message: _Optional[str] = ..., error: _Optional[str] = ..., stack: _Optional[str] = ...) -> None: ... class CallEvent(_message.Message): - __slots__ = ("request_key", "deployment_key", "time_stamp", "source_verb_ref", "destination_verb_ref", "duration", "request", "response", "error", "stack") + __slots__ = ("request_key", "deployment_key", "timestamp", "source_verb_ref", "destination_verb_ref", "duration", "request", "response", "error", "stack") REQUEST_KEY_FIELD_NUMBER: _ClassVar[int] DEPLOYMENT_KEY_FIELD_NUMBER: _ClassVar[int] - TIME_STAMP_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] SOURCE_VERB_REF_FIELD_NUMBER: _ClassVar[int] DESTINATION_VERB_REF_FIELD_NUMBER: _ClassVar[int] DURATION_FIELD_NUMBER: _ClassVar[int] @@ -97,7 +97,7 @@ class CallEvent(_message.Message): STACK_FIELD_NUMBER: _ClassVar[int] request_key: str deployment_key: str - time_stamp: _timestamp_pb2.Timestamp + timestamp: _timestamp_pb2.Timestamp source_verb_ref: _schema_pb2.Ref destination_verb_ref: _schema_pb2.Ref duration: _duration_pb2.Duration @@ -105,7 +105,7 @@ class CallEvent(_message.Message): response: str error: str stack: str - def __init__(self, request_key: _Optional[str] = ..., deployment_key: _Optional[str] = ..., time_stamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., source_verb_ref: _Optional[_Union[_schema_pb2.Ref, _Mapping]] = ..., destination_verb_ref: _Optional[_Union[_schema_pb2.Ref, _Mapping]] = ..., duration: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., request: _Optional[str] = ..., response: _Optional[str] = ..., error: _Optional[str] = ..., stack: _Optional[str] = ...) -> None: ... + def __init__(self, request_key: _Optional[str] = ..., deployment_key: _Optional[str] = ..., timestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., source_verb_ref: _Optional[_Union[_schema_pb2.Ref, _Mapping]] = ..., destination_verb_ref: _Optional[_Union[_schema_pb2.Ref, _Mapping]] = ..., duration: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., request: _Optional[str] = ..., response: _Optional[str] = ..., error: _Optional[str] = ..., stack: _Optional[str] = ...) -> None: ... class DeploymentCreatedEvent(_message.Message): __slots__ = ("key", "language", "module_name", "min_replicas", "replaced") @@ -132,14 +132,14 @@ class DeploymentUpdatedEvent(_message.Message): def __init__(self, key: _Optional[str] = ..., min_replicas: _Optional[int] = ..., prev_min_replicas: _Optional[int] = ...) -> None: ... class IngressEvent(_message.Message): - __slots__ = ("deployment_key", "request_key", "verb_ref", "method", "path", "status_code", "time_stamp", "duration", "request", "request_header", "response", "response_header", "error") + __slots__ = ("deployment_key", "request_key", "verb_ref", "method", "path", "status_code", "timestamp", "duration", "request", "request_header", "response", "response_header", "error") DEPLOYMENT_KEY_FIELD_NUMBER: _ClassVar[int] REQUEST_KEY_FIELD_NUMBER: _ClassVar[int] VERB_REF_FIELD_NUMBER: _ClassVar[int] METHOD_FIELD_NUMBER: _ClassVar[int] PATH_FIELD_NUMBER: _ClassVar[int] STATUS_CODE_FIELD_NUMBER: _ClassVar[int] - TIME_STAMP_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] DURATION_FIELD_NUMBER: _ClassVar[int] REQUEST_FIELD_NUMBER: _ClassVar[int] REQUEST_HEADER_FIELD_NUMBER: _ClassVar[int] @@ -152,57 +152,57 @@ class IngressEvent(_message.Message): method: str path: str status_code: int - time_stamp: _timestamp_pb2.Timestamp + timestamp: _timestamp_pb2.Timestamp duration: _duration_pb2.Duration request: str request_header: str response: str response_header: str error: str - def __init__(self, deployment_key: _Optional[str] = ..., request_key: _Optional[str] = ..., verb_ref: _Optional[_Union[_schema_pb2.Ref, _Mapping]] = ..., method: _Optional[str] = ..., path: _Optional[str] = ..., status_code: _Optional[int] = ..., time_stamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., duration: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., request: _Optional[str] = ..., request_header: _Optional[str] = ..., response: _Optional[str] = ..., response_header: _Optional[str] = ..., error: _Optional[str] = ...) -> None: ... + def __init__(self, deployment_key: _Optional[str] = ..., request_key: _Optional[str] = ..., verb_ref: _Optional[_Union[_schema_pb2.Ref, _Mapping]] = ..., method: _Optional[str] = ..., path: _Optional[str] = ..., status_code: _Optional[int] = ..., timestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., duration: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., request: _Optional[str] = ..., request_header: _Optional[str] = ..., response: _Optional[str] = ..., response_header: _Optional[str] = ..., error: _Optional[str] = ...) -> None: ... class CronScheduledEvent(_message.Message): - __slots__ = ("deployment_key", "verb_ref", "time_stamp", "duration", "scheduled_at", "schedule", "error") + __slots__ = ("deployment_key", "verb_ref", "timestamp", "duration", "scheduled_at", "schedule", "error") DEPLOYMENT_KEY_FIELD_NUMBER: _ClassVar[int] VERB_REF_FIELD_NUMBER: _ClassVar[int] - TIME_STAMP_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] DURATION_FIELD_NUMBER: _ClassVar[int] SCHEDULED_AT_FIELD_NUMBER: _ClassVar[int] SCHEDULE_FIELD_NUMBER: _ClassVar[int] ERROR_FIELD_NUMBER: _ClassVar[int] deployment_key: str verb_ref: _schema_pb2.Ref - time_stamp: _timestamp_pb2.Timestamp + timestamp: _timestamp_pb2.Timestamp duration: _duration_pb2.Duration scheduled_at: _timestamp_pb2.Timestamp schedule: str error: str - def __init__(self, deployment_key: _Optional[str] = ..., verb_ref: _Optional[_Union[_schema_pb2.Ref, _Mapping]] = ..., time_stamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., duration: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., scheduled_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., schedule: _Optional[str] = ..., error: _Optional[str] = ...) -> None: ... + def __init__(self, deployment_key: _Optional[str] = ..., verb_ref: _Optional[_Union[_schema_pb2.Ref, _Mapping]] = ..., timestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., duration: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., scheduled_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., schedule: _Optional[str] = ..., error: _Optional[str] = ...) -> None: ... class AsyncExecuteEvent(_message.Message): - __slots__ = ("deployment_key", "request_key", "verb_ref", "time_stamp", "duration", "async_event_type", "error") + __slots__ = ("deployment_key", "request_key", "verb_ref", "timestamp", "duration", "async_event_type", "error") DEPLOYMENT_KEY_FIELD_NUMBER: _ClassVar[int] REQUEST_KEY_FIELD_NUMBER: _ClassVar[int] VERB_REF_FIELD_NUMBER: _ClassVar[int] - TIME_STAMP_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] DURATION_FIELD_NUMBER: _ClassVar[int] ASYNC_EVENT_TYPE_FIELD_NUMBER: _ClassVar[int] ERROR_FIELD_NUMBER: _ClassVar[int] deployment_key: str request_key: str verb_ref: _schema_pb2.Ref - time_stamp: _timestamp_pb2.Timestamp + timestamp: _timestamp_pb2.Timestamp duration: _duration_pb2.Duration async_event_type: AsyncExecuteEventType error: str - def __init__(self, deployment_key: _Optional[str] = ..., request_key: _Optional[str] = ..., verb_ref: _Optional[_Union[_schema_pb2.Ref, _Mapping]] = ..., time_stamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., duration: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., async_event_type: _Optional[_Union[AsyncExecuteEventType, str]] = ..., error: _Optional[str] = ...) -> None: ... + def __init__(self, deployment_key: _Optional[str] = ..., request_key: _Optional[str] = ..., verb_ref: _Optional[_Union[_schema_pb2.Ref, _Mapping]] = ..., timestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., duration: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., async_event_type: _Optional[_Union[AsyncExecuteEventType, str]] = ..., error: _Optional[str] = ...) -> None: ... class PubSubPublishEvent(_message.Message): - __slots__ = ("deployment_key", "request_key", "verb_ref", "time_stamp", "duration", "topic", "request", "error") + __slots__ = ("deployment_key", "request_key", "verb_ref", "timestamp", "duration", "topic", "request", "error") DEPLOYMENT_KEY_FIELD_NUMBER: _ClassVar[int] REQUEST_KEY_FIELD_NUMBER: _ClassVar[int] VERB_REF_FIELD_NUMBER: _ClassVar[int] - TIME_STAMP_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] DURATION_FIELD_NUMBER: _ClassVar[int] TOPIC_FIELD_NUMBER: _ClassVar[int] REQUEST_FIELD_NUMBER: _ClassVar[int] @@ -210,20 +210,20 @@ class PubSubPublishEvent(_message.Message): deployment_key: str request_key: str verb_ref: _schema_pb2.Ref - time_stamp: _timestamp_pb2.Timestamp + timestamp: _timestamp_pb2.Timestamp duration: _duration_pb2.Duration topic: str request: str error: str - def __init__(self, deployment_key: _Optional[str] = ..., request_key: _Optional[str] = ..., verb_ref: _Optional[_Union[_schema_pb2.Ref, _Mapping]] = ..., time_stamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., duration: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., topic: _Optional[str] = ..., request: _Optional[str] = ..., error: _Optional[str] = ...) -> None: ... + def __init__(self, deployment_key: _Optional[str] = ..., request_key: _Optional[str] = ..., verb_ref: _Optional[_Union[_schema_pb2.Ref, _Mapping]] = ..., timestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., duration: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., topic: _Optional[str] = ..., request: _Optional[str] = ..., error: _Optional[str] = ...) -> None: ... class PubSubConsumeEvent(_message.Message): - __slots__ = ("deployment_key", "request_key", "dest_verb_module", "dest_verb_name", "time_stamp", "duration", "topic", "error") + __slots__ = ("deployment_key", "request_key", "dest_verb_module", "dest_verb_name", "timestamp", "duration", "topic", "error") DEPLOYMENT_KEY_FIELD_NUMBER: _ClassVar[int] REQUEST_KEY_FIELD_NUMBER: _ClassVar[int] DEST_VERB_MODULE_FIELD_NUMBER: _ClassVar[int] DEST_VERB_NAME_FIELD_NUMBER: _ClassVar[int] - TIME_STAMP_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] DURATION_FIELD_NUMBER: _ClassVar[int] TOPIC_FIELD_NUMBER: _ClassVar[int] ERROR_FIELD_NUMBER: _ClassVar[int] @@ -231,15 +231,15 @@ class PubSubConsumeEvent(_message.Message): request_key: str dest_verb_module: str dest_verb_name: str - time_stamp: _timestamp_pb2.Timestamp + timestamp: _timestamp_pb2.Timestamp duration: _duration_pb2.Duration topic: str error: str - def __init__(self, deployment_key: _Optional[str] = ..., request_key: _Optional[str] = ..., dest_verb_module: _Optional[str] = ..., dest_verb_name: _Optional[str] = ..., time_stamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., duration: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., topic: _Optional[str] = ..., error: _Optional[str] = ...) -> None: ... + def __init__(self, deployment_key: _Optional[str] = ..., request_key: _Optional[str] = ..., dest_verb_module: _Optional[str] = ..., dest_verb_name: _Optional[str] = ..., timestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., duration: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., topic: _Optional[str] = ..., error: _Optional[str] = ...) -> None: ... class Event(_message.Message): - __slots__ = ("time_stamp", "id", "log", "call", "deployment_created", "deployment_updated", "ingress", "cron_scheduled", "async_execute", "pubsub_publish", "pubsub_consume") - TIME_STAMP_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("timestamp", "id", "log", "call", "deployment_created", "deployment_updated", "ingress", "cron_scheduled", "async_execute", "pubsub_publish", "pubsub_consume") + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] ID_FIELD_NUMBER: _ClassVar[int] LOG_FIELD_NUMBER: _ClassVar[int] CALL_FIELD_NUMBER: _ClassVar[int] @@ -250,7 +250,7 @@ class Event(_message.Message): ASYNC_EXECUTE_FIELD_NUMBER: _ClassVar[int] PUBSUB_PUBLISH_FIELD_NUMBER: _ClassVar[int] PUBSUB_CONSUME_FIELD_NUMBER: _ClassVar[int] - time_stamp: _timestamp_pb2.Timestamp + timestamp: _timestamp_pb2.Timestamp id: int log: LogEvent call: CallEvent @@ -261,4 +261,4 @@ class Event(_message.Message): async_execute: AsyncExecuteEvent pubsub_publish: PubSubPublishEvent pubsub_consume: PubSubConsumeEvent - def __init__(self, time_stamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., id: _Optional[int] = ..., log: _Optional[_Union[LogEvent, _Mapping]] = ..., call: _Optional[_Union[CallEvent, _Mapping]] = ..., deployment_created: _Optional[_Union[DeploymentCreatedEvent, _Mapping]] = ..., deployment_updated: _Optional[_Union[DeploymentUpdatedEvent, _Mapping]] = ..., ingress: _Optional[_Union[IngressEvent, _Mapping]] = ..., cron_scheduled: _Optional[_Union[CronScheduledEvent, _Mapping]] = ..., async_execute: _Optional[_Union[AsyncExecuteEvent, _Mapping]] = ..., pubsub_publish: _Optional[_Union[PubSubPublishEvent, _Mapping]] = ..., pubsub_consume: _Optional[_Union[PubSubConsumeEvent, _Mapping]] = ...) -> None: ... + def __init__(self, timestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., id: _Optional[int] = ..., log: _Optional[_Union[LogEvent, _Mapping]] = ..., call: _Optional[_Union[CallEvent, _Mapping]] = ..., deployment_created: _Optional[_Union[DeploymentCreatedEvent, _Mapping]] = ..., deployment_updated: _Optional[_Union[DeploymentUpdatedEvent, _Mapping]] = ..., ingress: _Optional[_Union[IngressEvent, _Mapping]] = ..., cron_scheduled: _Optional[_Union[CronScheduledEvent, _Mapping]] = ..., async_execute: _Optional[_Union[AsyncExecuteEvent, _Mapping]] = ..., pubsub_publish: _Optional[_Union[PubSubPublishEvent, _Mapping]] = ..., pubsub_consume: _Optional[_Union[PubSubConsumeEvent, _Mapping]] = ...) -> None: ... diff --git a/sqlc.yaml b/sqlc.yaml index 609a22d7fe..60f45a30fc 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -6,8 +6,6 @@ sql: - backend/controller/dal/internal/sql/queries.sql - backend/controller/dal/internal/sql/async_queries.sql - backend/controller/pubsub/internal/sql/queries.sql - # Some of the timeline entries happen within a controller transaction, so we need to include them here - - backend/controller/timeline/internal/sql/deployment_queries.sql schema: "backend/controller/sql/schema" database: uri: postgres://localhost:15432/ftl?sslmode=disable&user=postgres&password=secret @@ -166,14 +164,6 @@ sql: go: <<: *gengo out: "backend/controller/encryption/internal/sql" - - <<: *daldir - queries: - - backend/controller/timeline/internal/sql/queries.sql - - backend/controller/timeline/internal/sql/deployment_queries.sql - gen: - go: - <<: *gengo - out: "backend/controller/timeline/internal/sql" - <<: *daldir queries: - backend/controller/pubsub/internal/sql/queries.sql