Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 4115a09

Browse files
committed
Usage metric pushing
1 parent c2eda4a commit 4115a09

File tree

7 files changed

+149
-19
lines changed

7 files changed

+149
-19
lines changed

cmd/coder/shell.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"go.coder.com/cli"
1717
"go.coder.com/flog"
1818

19+
"cdr.dev/coder-cli/internal/activity"
1920
"cdr.dev/wsep"
2021
)
2122

@@ -145,7 +146,11 @@ func runCommand(ctx context.Context, envName string, command string, args []stri
145146
go func() {
146147
stdin := process.Stdin()
147148
defer stdin.Close()
148-
_, err := io.Copy(stdin, os.Stdin)
149+
150+
ap := &activity.Pusher{Source: "ssh", EnvID: env.ID, Client: entClient}
151+
defer ap.Start()()
152+
153+
_, err := activity.Copy(ap, stdin, os.Stdin)
149154
if err != nil {
150155
cancel()
151156
}

cmd/coder/sync.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@ func (cmd *syncCmd) Run(fl *pflag.FlagSet) {
6969
}
7070

7171
s := sync.Sync{
72-
Init: cmd.init,
73-
Environment: env,
74-
RemoteDir: remoteDir,
75-
LocalDir: absLocal,
76-
Client: entClient,
72+
Init: cmd.init,
73+
Env: env,
74+
RemoteDir: remoteDir,
75+
LocalDir: absLocal,
76+
Client: entClient,
7777
}
7878
for err == nil || err == sync.ErrRestartSync {
7979
err = s.Run()

cmd/coder/url.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ import (
1111
"go.coder.com/flog"
1212
)
1313

14-
type urlCmd struct {
15-
}
14+
type urlCmd struct{}
1615

1716
type DevURL struct {
1817
Url string `json:"url"`

internal/activity/copy.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package activity
2+
3+
import "io"
4+
5+
// Copy copyes src to dst, pushing activity when bytes are read from src.
6+
func Copy(p *Pusher, dst io.Writer, src io.Reader) (written int64, err error) {
7+
buf := make([]byte, 32*1024)
8+
9+
for {
10+
nr, er := src.Read(buf)
11+
if nr > 0 {
12+
p.Push()
13+
14+
nw, ew := dst.Write(buf[0:nr])
15+
if nw > 0 {
16+
written += int64(nw)
17+
}
18+
if ew != nil {
19+
err = ew
20+
break
21+
}
22+
if nr != nw {
23+
err = io.ErrShortWrite
24+
break
25+
}
26+
}
27+
if er != nil {
28+
if er != io.EOF {
29+
err = er
30+
}
31+
break
32+
}
33+
}
34+
35+
return written, err
36+
}

internal/activity/pusher.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package activity
2+
3+
import (
4+
"context"
5+
"sync/atomic"
6+
"time"
7+
8+
"cdr.dev/coder-cli/internal/entclient"
9+
"go.coder.com/flog"
10+
)
11+
12+
const pushInterval = time.Minute
13+
14+
// Pusher pushes activity metrics no more than once per pushInterval. Pushes
15+
// within the same interval are a no-op.
16+
type Pusher struct {
17+
Source string
18+
EnvID string
19+
Client *entclient.Client
20+
21+
state int64
22+
}
23+
24+
func (p *Pusher) Push() {
25+
if atomic.CompareAndSwapInt64(&p.state, 0, 1) {
26+
err := p.Client.PushActivity(p.Source, p.EnvID)
27+
if err != nil {
28+
flog.Error("failed to push activity: %s", err.Error())
29+
}
30+
}
31+
}
32+
33+
// Start starts the reset routine. It resets the state every pushInterval,
34+
// allowing Push to push a new activity.
35+
func (p *Pusher) Start() (end func()) {
36+
ctx, cancel := context.WithCancel(context.Background())
37+
go func() {
38+
tick := time.NewTicker(pushInterval)
39+
defer tick.Stop()
40+
41+
for {
42+
select {
43+
case <-ctx.Done():
44+
return
45+
case <-tick.C:
46+
atomic.StoreInt64(&p.state, 0)
47+
}
48+
49+
}
50+
}()
51+
52+
return func() {
53+
cancel()
54+
}
55+
}

internal/entclient/activity.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package entclient
2+
3+
import "net/http"
4+
5+
func (c Client) PushActivity(source string, envID string) error {
6+
res, err := c.request("POST", "/api/metrics/usage/push", map[string]string{
7+
"source": source,
8+
"environment_id": envID,
9+
})
10+
if err != nil {
11+
return err
12+
}
13+
14+
if res.StatusCode != http.StatusOK {
15+
return bodyError(res)
16+
}
17+
18+
return nil
19+
}

internal/sync/sync.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"go.coder.com/flog"
2323

24+
"cdr.dev/coder-cli/internal/activity"
2425
"cdr.dev/coder-cli/internal/entclient"
2526
"cdr.dev/wsep"
2627
)
@@ -33,8 +34,11 @@ type Sync struct {
3334
LocalDir string
3435
// RemoteDir is an absolute path.
3536
RemoteDir string
36-
entclient.Environment
37-
*entclient.Client
37+
// DisableMetrics disables activity metric pushing.
38+
DisableMetrics bool
39+
40+
Env entclient.Environment
41+
Client *entclient.Client
3842
}
3943

4044
func (s Sync) syncPaths(delete bool, local, remote string) error {
@@ -43,7 +47,7 @@ func (s Sync) syncPaths(delete bool, local, remote string) error {
4347
args := []string{"-zz",
4448
"-a",
4549
"--delete",
46-
"-e", self + " sh", local, s.Environment.Name + ":" + remote,
50+
"-e", self + " sh", local, s.Env.Name + ":" + remote,
4751
}
4852
if delete {
4953
args = append([]string{"--delete"}, args...)
@@ -68,7 +72,7 @@ func (s Sync) syncPaths(delete bool, local, remote string) error {
6872
}
6973

7074
func (s Sync) remoteRm(ctx context.Context, remote string) error {
71-
conn, err := s.Client.DialWsep(ctx, s.Environment)
75+
conn, err := s.Client.DialWsep(ctx, s.Env)
7276
if err != nil {
7377
return err
7478
}
@@ -229,13 +233,16 @@ func (s Sync) workEventGroup(evs []timedEvent) {
229233
}
230234

231235
const (
232-
// maxinflightInotify sets the maximum number of inotifies before the sync just restarts.
233-
// Syncing a large amount of small files (e.g .git or node_modules) is impossible to do performantly
234-
// with individual rsyncs.
236+
// maxinflightInotify sets the maximum number of inotifies before the
237+
// sync just restarts. Syncing a large amount of small files (e.g .git
238+
// or node_modules) is impossible to do performantly with individual
239+
// rsyncs.
235240
maxInflightInotify = 8
236241
maxEventDelay = time.Second * 7
237-
// maxAcceptableDispatch is the maximum amount of time before an event should begin its journey to the server.
238-
// This sets a lower bound for perceivable latency, but the higher it is, the better the optimization.
242+
// maxAcceptableDispatch is the maximum amount of time before an event
243+
// should begin its journey to the server. This sets a lower bound for
244+
// perceivable latency, but the higher it is, the better the
245+
// optimization.
239246
maxAcceptableDispatch = time.Millisecond * 50
240247
)
241248

@@ -245,13 +252,18 @@ const (
245252
func (s Sync) Run() error {
246253
events := make(chan notify.EventInfo, maxInflightInotify)
247254
// Set up a recursive watch.
248-
// We do this before the initial sync so we can capture any changes that may have happened during sync.
255+
// We do this before the initial sync so we can capture any changes
256+
// that may have happened during sync.
249257
err := notify.Watch(path.Join(s.LocalDir, "..."), events, notify.All)
250258
if err != nil {
251259
return xerrors.Errorf("create watch: %w", err)
252260
}
253261
defer notify.Stop(events)
254262

263+
ap := activity.Pusher{Source: activityName, EnvID: s.Env.ID, Client: s.Client}
264+
defer ap.Start()()
265+
ap.Push()
266+
255267
setConsoleTitle("⏳ syncing project")
256268
err = s.initSync()
257269
if err != nil {
@@ -265,7 +277,8 @@ func (s Sync) Run() error {
265277
flog.Info("watching %s for changes", s.LocalDir)
266278

267279
var droppedEvents uint64
268-
// Timed events lets us track how long each individual file takes to update.
280+
// Timed events lets us track how long each individual file takes to
281+
// update.
269282
timedEvents := make(chan timedEvent, cap(events))
270283
go func() {
271284
defer close(timedEvents)
@@ -309,6 +322,9 @@ func (s Sync) Run() error {
309322
}
310323
s.workEventGroup(eventGroup)
311324
eventGroup = eventGroup[:0]
325+
ap.Push()
312326
}
313327
}
314328
}
329+
330+
const activityName = "sync"

0 commit comments

Comments
 (0)