diff --git a/core/api/gql/pagination.go b/core/api/gql/pagination.go new file mode 100644 index 00000000..6dfc371c --- /dev/null +++ b/core/api/gql/pagination.go @@ -0,0 +1 @@ +package gql diff --git a/core/api/gql/schema.graphql b/core/api/gql/schema.graphql index 93cb6798..d571d9ee 100644 --- a/core/api/gql/schema.graphql +++ b/core/api/gql/schema.graphql @@ -362,7 +362,7 @@ type Team { owner: User! maxGroupOrderIndex: Int! members: [TeamMember!]! - tasks(filter: TaskFilter): [Task!]! + tasks(filter: TaskFilter, sort: TaskSort, pagination: Pagination): Tasks! invitations(filter: InvitationFilter): [Invitation!]! sprints(filter: SprintFilter): [Sprint!]! appInstallations: [TeamAppInstallation!]! @@ -373,6 +373,41 @@ type Team { memberGroups: [TeamMemberGroup!]! } +type Pagination { + pageSize: Int! + afterCursor: String +} + + +type Tasks { + edges: [TaskEdge!]! + pageInfo: PageInfo! +} + +type PageInfo { + hasNextPage: Boolean! + endCursor: String +} + +type TaskSort { + order: SortOrder! + field: TaskSortField! +} + +enum SortOrder { + ASC + DESC +} + +enum TaskSortField { + Priority +} + +type TaskEdge { + cursor: String! + node: Task! +} + type TeamMember { team: Team! user: User! diff --git a/core/api/gql/sort.go b/core/api/gql/sort.go new file mode 100644 index 00000000..2301de58 --- /dev/null +++ b/core/api/gql/sort.go @@ -0,0 +1,14 @@ +package gql + +type SortOrder string + +const ( + ASC SortOrder = "ASC" + DESC SortOrder = "DESC" +) + +type TaskSortField string + +const ( + TaskSortFieldPriority TaskSortField = "PRIORITY" +) diff --git a/core/api/gql/task.go b/core/api/gql/task.go index a5ced954..c072fa49 100644 --- a/core/api/gql/task.go +++ b/core/api/gql/task.go @@ -174,3 +174,45 @@ func newTask(deps *Dependencies, task entity.Task) Task { task: task, } } + +type TaskEdge struct { + deps *Dependencies + edge entity.TaskEdge +} + +func (t TaskEdge) Cursor(ct context.Context) string { + return t.edge.Cursor +} + +func (t TaskEdge) Node(ct context.Context) Task { + return newTask(t.deps, t.edge.Node) +} + +func newTaskEdge(deps *Dependencies, edge entity.TaskEdge) TaskEdge { + return TaskEdge{ + deps: deps, + edge: edge, + } +} + +type Tasks struct { + deps *Dependencies + tasks entity.Tasks +} + +func (t Tasks) Edges(ct context.Context) []TaskEdge { + return collect.Map(t.tasks.Edges, func(edge entity.TaskEdge, _ int) TaskEdge { + return newTaskEdge(t.deps, edge) + }) +} + +func (t Tasks) PageInfo(ct context.Context) PageInfo { + +} + +func newTasks(deps *Dependencies, tasks entity.Tasks) Tasks { + return Tasks{ + deps: deps, + tasks: tasks, + } +} diff --git a/core/api/gql/team.go b/core/api/gql/team.go index 32a550ee..50e8b859 100644 --- a/core/api/gql/team.go +++ b/core/api/gql/team.go @@ -102,7 +102,15 @@ func (t Team) Projects(ct context.Context) ([]Project, error) { func (t Team) Tasks(ct context.Context, args struct { Filter *TaskFilter -}) ([]Task, error) { + Sort *struct { + Field TaskSortField + Order SortOrder + } + Pagination *struct { + PageSize int32 + AfterCursor *string + } +}) (Tasks, error) { filter, argErr := fromGraphQLTaskFilterPtr(args.Filter) if argErr != nil { internalErr := errs.NewError( diff --git a/core/entity/pagination.go b/core/entity/pagination.go new file mode 100644 index 00000000..f7d01d0a --- /dev/null +++ b/core/entity/pagination.go @@ -0,0 +1,16 @@ +package entity + +type PageInfo struct { + HasNextPage bool + EndCursor *string +} + +type TaskEdge struct { + Cursor string + Node Task +} + +type Tasks struct { + Edges []TaskEdge + PageInfo PageInfo +} diff --git a/core/service/sort.go b/core/service/sort.go new file mode 100644 index 00000000..7a6a9b2b --- /dev/null +++ b/core/service/sort.go @@ -0,0 +1,14 @@ +package service + +type SortOrder string + +const ( + SortOrderAsc SortOrder = "ASC" + SortOrderDesc SortOrder = "DESC" +) + +type TaskSortField string + +const ( + TaskSortFieldPriority TaskSortField = "PRIORITY" +) diff --git a/main.go b/main.go index 3f0f5d31..080fb00e 100644 --- a/main.go +++ b/main.go @@ -5,10 +5,13 @@ import ( "database/sql" _ "embed" "fmt" + "log" "math/rand" "net/http" "os" "path/filepath" + systemRuntime "runtime" + "runtime/pprof" "strings" "time" @@ -43,6 +46,42 @@ const serviceName = "backend" var serviceLabels = []string{appName, serviceName} var fullServiceName = strings.Join(serviceLabels, "-") +func dumpMemoryProfile(fileName string) error { + f, err := os.Create(fileName) + if err != nil { + return err + } + + defer f.Close() + + //runtime.GC() // get up-to-date statistics + if err := pprof.WriteHeapProfile(f); err != nil { + return err + } + + return nil +} + +func monitorMemoryUsage(threshold uint64) { + count := 0 + var m systemRuntime.MemStats + for { + systemRuntime.ReadMemStats(&m) + log.Printf("Alloc: %vMB, TotalAlloc:%vMB\n", m.Alloc/1024/1024, m.TotalAlloc/1024/1024) + if m.Alloc > threshold { + count++ + if count > 10 { + if err := dumpMemoryProfile("high_memory_profile.prof"); err != nil { + log.Printf("could not write memory profile: %v", err) + } + + return + } + } + + time.Sleep(500 * time.Millisecond) // adjust the interval as needed + } +} func init() { rand.Seed(time.Now().UnixNano()) } @@ -330,20 +369,22 @@ func startServiceRunner( return errs.NewError(errs.Unknown, err.Error()) } - backfillService, err := dep.InitBackfillService( - logger, - realTimeStateSyncer, cloudClientRegistry, - prom, - sqlDB, - ) - if err != nil { - return errs.NewError(errs.Unknown, err.Error()) - } - - internalErr = backfillService.BackfillData(ct) - if internalErr != nil { - return internalErr - } + // backfillService, err := dep.InitBackfillService( + // logger, + // realTimeStateSyncer, cloudClientRegistry, + // prom, + // sqlDB, + // ) + // if err != nil { + // return errs.NewError(errs.Unknown, err.Error()) + // } + + // internalErr = backfillService.BackfillData(ct) + // if internalErr != nil { + // return internalErr + // } + + go monitorMemoryUsage(1024 * 1024 * 1024 / 2) // 512MB rn := runner.NewServiceRunnerBuilder( logger,