Skip to content

Commit d893431

Browse files
committed
Refactor to use in-memory ws activity map
1 parent 13d5ac4 commit d893431

File tree

6 files changed

+62
-44
lines changed

6 files changed

+62
-44
lines changed

components/ws-manager-api/go/crd/v1/workspace_types.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,6 @@ const (
147147
// UserActivity is the time when MarkActive was first called on the workspace
148148
WorkspaceConditionFirstUserActivity WorkspaceCondition = "FirstUserActivity"
149149

150-
// UserActivity is the most recent time when MarkActive was called on the workspace
151-
WorkspaceConditionUserActivity WorkspaceCondition = "UserActivity"
152-
153150
// Closed indicates that a workspace is marked as closed. This will shorten its timeout.
154151
WorkspaceConditionClosed WorkspaceCondition = "Closed"
155152

components/ws-manager-mk2/controllers/status.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/gitpod-io/gitpod/common-go/util"
1515

16+
wsactivity "github.com/gitpod-io/gitpod/ws-manager-mk2/pkg/activity"
1617
"github.com/gitpod-io/gitpod/ws-manager/api/config"
1718
workspacev1 "github.com/gitpod-io/gitpod/ws-manager/api/crd/v1"
1819
corev1 "k8s.io/api/core/v1"
@@ -309,8 +310,10 @@ const (
309310
)
310311

311312
// isWorkspaceTimedOut determines if a workspace is timed out based on the manager configuration and state the pod is in.
312-
// This function does NOT use the workspaceTimedoutAnnotation, but rather is used to set that annotation in the first place.
313-
func isWorkspaceTimedOut(ws *workspacev1.Workspace, pod *corev1.Pod, timeouts config.WorkspaceTimeoutConfiguration) (reason string, err error) {
313+
// This function does NOT use the Timeout condition, but rather is used to set that condition in the first place.
314+
//
315+
//nolint:unused,deadcode TODO: Remove nolint
316+
func isWorkspaceTimedOut(ws *workspacev1.Workspace, pod *corev1.Pod, timeouts config.WorkspaceTimeoutConfiguration, act *wsactivity.WorkspaceActivity) (reason string, err error) {
314317
// workspaceID := ws.Spec.Ownership.WorkspaceID
315318
phase := ws.Status.Phase
316319

@@ -326,7 +329,7 @@ func isWorkspaceTimedOut(ws *workspacev1.Workspace, pod *corev1.Pod, timeouts co
326329

327330
// TODO: Use ws or pod's CreationTimestamp?
328331
start := ws.ObjectMeta.CreationTimestamp.Time
329-
lastActivity := getWorkspaceActivity(ws)
332+
lastActivity := act.GetLastActivity(ws.Spec.Ownership.WorkspaceID)
330333
isClosed := conditionPresentAndTrue(ws.Status.Conditions, string(workspacev1.WorkspaceConditionClosed))
331334

332335
switch phase {
@@ -385,15 +388,7 @@ func isWorkspaceTimedOut(ws *workspacev1.Workspace, pod *corev1.Pod, timeouts co
385388
}
386389
}
387390

388-
func getWorkspaceActivity(ws *workspacev1.Workspace) *time.Time {
389-
for _, c := range ws.Status.Conditions {
390-
if c.Type == string(workspacev1.WorkspaceConditionUserActivity) {
391-
return &c.LastTransitionTime.Time
392-
}
393-
}
394-
return nil
395-
}
396-
391+
//nolint:unused,deadcode TODO: remove nolint
397392
func formatDuration(d time.Duration) string {
398393
d = d.Round(time.Minute)
399394
h := d / time.Hour

components/ws-manager-mk2/controllers/workspace_controller.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"sigs.k8s.io/controller-runtime/pkg/client"
1717
"sigs.k8s.io/controller-runtime/pkg/log"
1818

19+
wsactivity "github.com/gitpod-io/gitpod/ws-manager-mk2/pkg/activity"
1920
config "github.com/gitpod-io/gitpod/ws-manager/api/config"
2021
workspacev1 "github.com/gitpod-io/gitpod/ws-manager/api/crd/v1"
2122
"github.com/prometheus/client_golang/prometheus"
@@ -28,11 +29,12 @@ const (
2829
kubernetesOperationTimeout = 5 * time.Second
2930
)
3031

31-
func NewWorkspaceReconciler(c client.Client, scheme *runtime.Scheme, cfg config.Configuration, reg prometheus.Registerer) (*WorkspaceReconciler, error) {
32+
func NewWorkspaceReconciler(c client.Client, scheme *runtime.Scheme, cfg config.Configuration, reg prometheus.Registerer, activity *wsactivity.WorkspaceActivity) (*WorkspaceReconciler, error) {
3233
reconciler := &WorkspaceReconciler{
33-
Client: c,
34-
Scheme: scheme,
35-
Config: cfg,
34+
Client: c,
35+
Scheme: scheme,
36+
Config: cfg,
37+
activity: activity,
3638
}
3739

3840
metrics, err := newControllerMetrics(reconciler)
@@ -52,6 +54,7 @@ type WorkspaceReconciler struct {
5254

5355
Config config.Configuration
5456
metrics *controllerMetrics
57+
activity *wsactivity.WorkspaceActivity
5558
OnReconcile func(ctx context.Context, ws *workspacev1.Workspace)
5659
}
5760

components/ws-manager-mk2/main.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"github.com/gitpod-io/gitpod/common-go/pprof"
4040
regapi "github.com/gitpod-io/gitpod/registry-facade/api"
4141
"github.com/gitpod-io/gitpod/ws-manager-mk2/controllers"
42+
"github.com/gitpod-io/gitpod/ws-manager-mk2/pkg/activity"
4243
"github.com/gitpod-io/gitpod/ws-manager-mk2/service"
4344
wsmanapi "github.com/gitpod-io/gitpod/ws-manager/api"
4445
config "github.com/gitpod-io/gitpod/ws-manager/api/config"
@@ -97,13 +98,14 @@ func main() {
9798
os.Exit(1)
9899
}
99100

100-
reconciler, err := controllers.NewWorkspaceReconciler(mgr.GetClient(), mgr.GetScheme(), cfg.Manager, metrics.Registry)
101+
activity := &activity.WorkspaceActivity{}
102+
reconciler, err := controllers.NewWorkspaceReconciler(mgr.GetClient(), mgr.GetScheme(), cfg.Manager, metrics.Registry, activity)
101103
if err != nil {
102104
setupLog.Error(err, "unable to create controller", "controller", "Workspace")
103105
os.Exit(1)
104106
}
105107

106-
wsmanService, err := setupGRPCService(cfg, mgr.GetClient())
108+
wsmanService, err := setupGRPCService(cfg, mgr.GetClient(), activity)
107109
if err != nil {
108110
setupLog.Error(err, "unable to start manager service")
109111
os.Exit(1)
@@ -137,7 +139,7 @@ func main() {
137139
}
138140
}
139141

140-
func setupGRPCService(cfg *config.ServiceConfiguration, k8s client.Client) (*service.WorkspaceManagerServer, error) {
142+
func setupGRPCService(cfg *config.ServiceConfiguration, k8s client.Client, activity *activity.WorkspaceActivity) (*service.WorkspaceManagerServer, error) {
141143
// TODO(cw): remove use of common-go/log
142144

143145
if len(cfg.RPCServer.RateLimits) > 0 {
@@ -170,7 +172,7 @@ func setupGRPCService(cfg *config.ServiceConfiguration, k8s client.Client) (*ser
170172

171173
grpcOpts = append(grpcOpts, grpc.UnknownServiceHandler(proxy.TransparentHandler(imagebuilderDirector(cfg.ImageBuilderProxy.TargetAddr))))
172174

173-
srv := service.NewWorkspaceManagerServer(k8s, &cfg.Manager, metrics.Registry)
175+
srv := service.NewWorkspaceManagerServer(k8s, &cfg.Manager, metrics.Registry, activity)
174176

175177
grpcServer := grpc.NewServer(grpcOpts...)
176178
grpc_prometheus.Register(grpcServer)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License.AGPL.txt in the project root for license information.
4+
5+
package activity
6+
7+
import (
8+
"sync"
9+
"time"
10+
)
11+
12+
// WorkspaceActivity is used to track the last user activity per workspace. This is
13+
// stored in memory instead of on the Workspace resource to limit load on the k8s API,
14+
// as this value will update often for each workspace.
15+
type WorkspaceActivity struct {
16+
m sync.Map
17+
}
18+
19+
func (w *WorkspaceActivity) Store(workspaceId string, lastActivity time.Time) {
20+
w.m.Store(workspaceId, &lastActivity)
21+
}
22+
23+
func (w *WorkspaceActivity) GetLastActivity(workspaceId string) *time.Time {
24+
lastActivity, ok := w.m.Load(workspaceId)
25+
if ok {
26+
return lastActivity.(*time.Time)
27+
}
28+
return nil
29+
}

components/ws-manager-mk2/service/manager.go

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
wsk8s "github.com/gitpod-io/gitpod/common-go/kubernetes"
2525
"github.com/gitpod-io/gitpod/common-go/log"
2626
"github.com/gitpod-io/gitpod/common-go/tracing"
27+
"github.com/gitpod-io/gitpod/ws-manager-mk2/pkg/activity"
2728
"github.com/gitpod-io/gitpod/ws-manager/api"
2829
wsmanapi "github.com/gitpod-io/gitpod/ws-manager/api"
2930
"github.com/gitpod-io/gitpod/ws-manager/api/config"
@@ -41,24 +42,26 @@ import (
4142
"sigs.k8s.io/controller-runtime/pkg/client"
4243
)
4344

44-
func NewWorkspaceManagerServer(clnt client.Client, cfg *config.Configuration, reg prometheus.Registerer) *WorkspaceManagerServer {
45+
func NewWorkspaceManagerServer(clnt client.Client, cfg *config.Configuration, reg prometheus.Registerer, activity *activity.WorkspaceActivity) *WorkspaceManagerServer {
4546
metrics := newWorkspaceMetrics()
4647
reg.MustRegister(metrics)
4748

4849
return &WorkspaceManagerServer{
49-
Client: clnt,
50-
Config: cfg,
51-
metrics: metrics,
50+
Client: clnt,
51+
Config: cfg,
52+
metrics: metrics,
53+
activity: activity,
5254
subs: subscriptions{
5355
subscribers: make(map[string]chan *wsmanapi.SubscribeResponse),
5456
},
5557
}
5658
}
5759

5860
type WorkspaceManagerServer struct {
59-
Client client.Client
60-
Config *config.Configuration
61-
metrics *workspaceMetrics
61+
Client client.Client
62+
Config *config.Configuration
63+
metrics *workspaceMetrics
64+
activity *activity.WorkspaceActivity
6265

6366
subs subscriptions
6467
wsmanapi.UnimplementedWorkspaceManagerServer
@@ -328,21 +331,10 @@ func (wsm *WorkspaceManagerServer) MarkActive(ctx context.Context, req *wsmanapi
328331
return &api.MarkActiveResponse{}, nil
329332
}
330333

331-
// We do not keep the last activity as annotation on the workspace to limit the load we're placing
334+
// We do not keep the last activity in the workspace resource to limit the load we're placing
332335
// on the K8S master in check. Thus, this state lives locally in a map.
333-
// TODO: Check impact on k8s api, moving to CRD condition.
334336
now := time.Now().UTC()
335-
if err = wsm.modifyWorkspace(ctx, req.Id, true, func(ws *workspacev1.Workspace) error {
336-
ws.Status.Conditions = addUniqueCondition(ws.Status.Conditions, metav1.Condition{
337-
Type: string(workspacev1.WorkspaceConditionUserActivity),
338-
Status: metav1.ConditionTrue,
339-
LastTransitionTime: metav1.NewTime(now),
340-
})
341-
return nil
342-
}); err != nil {
343-
log.WithError(err).WithFields(log.OWI("", "", workspaceID)).Warn("was unable to set UserActivity condition on workspace")
344-
return nil, err
345-
}
337+
wsm.activity.Store(req.Id, now)
346338

347339
// We do however maintain the the "closed" flag as annotation on the workspace. This flag should not change
348340
// very often and provides a better UX if it persists across ws-manager restarts.
@@ -375,7 +367,7 @@ func (wsm *WorkspaceManagerServer) MarkActive(ctx context.Context, req *wsmanapi
375367
log.WithError(err).WithFields(log.OWI("", "", workspaceID)).Warn("was unable to mark workspace properly")
376368
}
377369

378-
// If it's the first call: Mark the pod with firstUserActivityAnnotation
370+
// If it's the first call: Mark the pod with FirstUserActivity condition.
379371
if firstUserActivity == nil {
380372
err := wsm.modifyWorkspace(ctx, req.Id, true, func(ws *workspacev1.Workspace) error {
381373
ws.Status.Conditions = append(ws.Status.Conditions, metav1.Condition{

0 commit comments

Comments
 (0)