-
Notifications
You must be signed in to change notification settings - Fork 218
/
analytics.go
135 lines (124 loc) · 3.52 KB
/
analytics.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package main
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/dghubble/sling"
"github.com/dickeyxxx/golock"
)
var analyticsPath = filepath.Join(CacheHome, "analytics.json")
var currentAnalyticsCommand = &AnalyticsCommand{
Timestamp: time.Now().Unix(),
Version: version(),
OS: runtime.GOOS,
Arch: runtime.GOARCH,
Language: "go/" + strings.TrimPrefix(runtime.Version(), "go"),
CLIVersion: version(),
Valid: true,
}
// AnalyticsCommand represents an analytics command
type AnalyticsCommand struct {
Command string `json:"command"`
Plugin string `json:"plugin,omitempty"`
Timestamp int64 `json:"timestamp"`
CLIVersion string `json:"cli_version"`
Version string `json:"version"`
OS string `json:"os"`
Arch string `json:"arch"`
Language string `json:"language"`
Status int `json:"status"`
Runtime int64 `json:"runtime"`
Valid bool `json:"valid"`
start time.Time
}
// RecordStart marks when a command was started (for tracking runtime)
func (c *AnalyticsCommand) RecordStart() {
c.start = time.Now()
}
// RecordEnd marks when a command was completed
// and records it to the analytics file
func (c *AnalyticsCommand) RecordEnd(status int) {
if c == nil || skipAnalytics() || len(Args) < 2 || (c.Valid && c.start.IsZero()) {
return
}
c.Command = Args[1]
c.Status = status
if !c.start.IsZero() {
c.Runtime = (time.Now().UnixNano() - c.start.UnixNano()) / 1000000
}
commands := readAnalyticsFile()
commands = append(commands, *c)
LogIfError(writeAnalyticsFile(commands))
}
func readAnalyticsFile() (commands []AnalyticsCommand) {
f, err := os.Open(analyticsPath)
if err != nil {
if !os.IsNotExist(err) {
LogIfError(err)
}
return
}
if err := json.NewDecoder(f).Decode(&commands); err != nil {
LogIfError(err)
}
return commands
}
func writeAnalyticsFile(commands []AnalyticsCommand) error {
data, err := json.MarshalIndent(commands, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(analyticsPath, data, 0644)
}
// SubmitAnalytics sends the analytics info to the analytics service
func SubmitAnalytics() {
if skipAnalytics() {
return
}
commands := readAnalyticsFile()
if len(commands) < 10 {
// do not record if less than 10 commands
return
}
lockfile := filepath.Join(CacheHome, "analytics.lock")
golock.Lock(lockfile)
defer golock.Unlock(lockfile)
commands = readAnalyticsFile() // read commands again in case it was locked
plugins := func() map[string]string {
plugins := make(map[string]string)
for _, plugin := range CorePlugins.Plugins() {
plugins[plugin.Name] = plugin.Version
}
for _, plugin := range UserPlugins.Plugins() {
plugins[plugin.Name] = plugin.Version
}
for _, p := range RubyPlugins() {
plugins[p] = "ruby"
}
return plugins
}
host := os.Getenv("HEROKU_ANALYTICS_HOST")
if host == "" {
host = "https://cli-analytics.heroku.com"
}
body := struct {
Version string `json:"version"`
Commands []AnalyticsCommand `json:"commands"`
User string `json:"user"`
Plugins map[string]string `json:"plugins"`
}{version(), commands, netrcLogin(), plugins()}
resp, err := sling.New().Base(host).Post("/record").BodyJSON(body).ReceiveSuccess(nil)
if err != nil {
LogIfError(err)
return
}
LogIfError(getHTTPError(resp))
writeAnalyticsFile([]AnalyticsCommand{})
}
func skipAnalytics() bool {
return os.Getenv("TESTING") == ONE || (config.SkipAnalytics != nil && *config.SkipAnalytics) || netrcLogin() == ""
}