-
Notifications
You must be signed in to change notification settings - Fork 224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add opt-out analytics to CLI (implements #108) #187
base: master
Are you sure you want to change the base?
Changes from all commits
61d6209
d655e2b
4e7f3ff
b50db32
2bc38cf
632c3df
897177b
c1636e2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Analytics Overview | ||
|
||
The OpenFaaS `faas-cli` utility has begun gathering anonymous aggregate analytics on its use and reporting these to Google Analytics. You will be notified the first time you run `faas-cli`. | ||
|
||
## Why? | ||
|
||
OpenFaaS and its CLI are provided free of charge and run entirely by community volunteers in their spare time. As a result, we do not have the resources to do detailed user studies of OpenFaaS users to decide how best to design future features and prioritise work. Anonymous aggregate user analytics allow us to prioritise fixes and features based on how, where and when people use Homebrew. | ||
|
||
## Where? | ||
|
||
The OpenFaaS project sends analytics events to Google Analytics via HTTPS. | ||
|
||
## What? | ||
|
||
The `faas-cli` currently shares the following information each time a command is invoked. | ||
|
||
- ProtocolVersion `v` - The Google Analytics Protocol version, currently `1`. | ||
- https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#v | ||
- Tracking ID `tid` - The `faas-cli` application tracking ID, e.g. `UA-107707760-2`. | ||
- https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#tid | ||
- Hit Type `t` - The type of analytics hit, `faas-cli` uses the `event` type. | ||
- https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#t | ||
- Client ID `cid` - A `faas-cli` analytics user ID, e.g. `c1481af7-8682-462f-a586-71d752dbe87b`. This is a UUID4 generated by the [`github.com/satori/go.uuid`](https://github.com/satori/go.uuid) package and stored in the local config file `~/.openfaas/analytics_uuid`. This does not allow us to track individual users but does enable us to accurately measure user counts vs. event counts. | ||
- https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cid | ||
- ApplicationName `an` - The application name, e.g. `faas-cli`. | ||
- https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#an | ||
- ApplicationVersion `av` - The `faas-cli` version, e.g. `0.4.18d`. | ||
- https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#av | ||
- Anonymize IP `aip` - The IP address will be anonymized when submitting the analytics event, e.g. `1`. | ||
- https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#aip | ||
- Language `cd1` - A Custom Dimension containing the function language for `new`, `build` and `deploy` commands (returns `unset` for all other subcommands). | ||
- https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cd_ | ||
- Operating System `cd2` - A Custom Dimension containing the OS on which `faas-cli` is being run, this is determined from `runtime.GOOS`. | ||
- https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cd_ | ||
- Architecture `cd3` - A Custom Dimension containing the Architecture on which `faas-cli` is being run, this is determined from `runtime.GOOS`. | ||
- https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cd_ | ||
- Event Category `ec` - The `faas-cli` groups all successful CLI actions under the `cli-success` category, and failures (not yet implemented) under `cli-failure`. | ||
- https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ec | ||
- Event Action `ea` - Each command invoked by the user is results in an analytics event being generated with the same name as the `faas-cli` subcommand. | ||
- https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ea | ||
|
||
## How? | ||
|
||
The core implementation is in the `analytics` package, with calls to `analytics.Event()` each time a subcommand is invoked. | ||
|
||
Analytics submission occurs in a background goroutine and will fail fast to avoid delaying any execution. | ||
|
||
Due to the speed with which most OpenFaaS API calls return, a timeout of 300 milliseconds is used to give the analytics submission time to complete. | ||
|
||
## Opting out? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you link me to what brew/ VSCode says? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
If you wish to opt out of submitting analytics events you may do so by setting the `OPEN_FAAS_TELEMETRY` environment variable. | ||
|
||
export OPEN_FAAS_TELEMETRY=0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Our NS should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't see where it was changed? The code review doesn't say "see outdated".. did you push yet? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missed pushing the README it seems. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// Copyright (c) Alex Ellis 2017. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
package analytics | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"path" | ||
|
||
homedir "github.com/mitchellh/go-homedir" | ||
"github.com/satori/go.uuid" | ||
) | ||
|
||
const analyticsUUIDFile = "analytics_uuid" | ||
|
||
func configDir() (string, error) { | ||
h, err := homedir.Dir() | ||
if err != nil { | ||
return "", fmt.Errorf("unable to detect homedir: %v", err) | ||
} | ||
fullpath, err := homedir.Expand(h) | ||
if err != nil { | ||
return "", fmt.Errorf("unable to expand homedir [%s]: %v", h, err) | ||
} | ||
|
||
return path.Clean(fullpath + "/.openfaas/"), nil | ||
} | ||
|
||
func configFile() string { | ||
dir, _ := configDir() | ||
return path.Clean(dir + "/" + analyticsUUIDFile) | ||
} | ||
|
||
func configDirExists() bool { | ||
dir, _ := configDir() | ||
if stat, err := os.Stat(dir); err == nil && stat.IsDir() { | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
func setUUID() (string, error) { | ||
if !configDirExists() { | ||
dir, _ := configDir() | ||
err := os.Mkdir(dir, 0700) | ||
if err != nil { | ||
return "", fmt.Errorf("Unable to create config dir: %v\n", err) | ||
} | ||
} | ||
uuidFile := configFile() | ||
uuidStr := uuid.NewV4().String() | ||
d1 := []byte(uuidStr) | ||
err := ioutil.WriteFile(uuidFile, d1, 0644) | ||
if err != nil { | ||
return "", fmt.Errorf("unable to write analytics ID file: %v", err) | ||
} | ||
fmt.Fprintln(os.Stderr, "# Creating analytics file in:", uuidFile) | ||
fmt.Fprintln(os.Stderr, "# Please see https://github.com/openfaas/faas-cli/blob/master/analytics.md for more information.") | ||
return uuidStr, nil | ||
} | ||
|
||
func getUUID() (string, error) { | ||
dat, err := ioutil.ReadFile(configFile()) | ||
if err != nil { | ||
return "", fmt.Errorf("Error reading file: %v", err) | ||
} | ||
uuidStr := string(dat) | ||
|
||
_, err = uuid.FromString(uuidStr) | ||
if err != nil { | ||
return "", fmt.Errorf("Unable to get valid UUID from file: %v", err) | ||
} | ||
|
||
return uuidStr, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// Copyright (c) Alex Ellis 2017. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
package analytics | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"runtime" | ||
|
||
"github.com/google/go-querystring/query" | ||
"github.com/openfaas/faas-cli/version" | ||
) | ||
|
||
const gaHost = "www.google-analytics.com" | ||
const trackingID = "UA-107707760-3" | ||
const applicationName = "faas-cli3" | ||
|
||
// disableEnvvar will prevent submission of analytics events if set | ||
const disableEnvvar = "OPEN_FAAS_TELEMETRY" | ||
|
||
// Event posts an analytics event to GA | ||
func Event(action string, language string, ch chan int) { | ||
if Disabled() { | ||
return | ||
} | ||
u, err := NewSession(language) | ||
if err != nil { | ||
return | ||
} | ||
u.EventAction = action | ||
go u.PostEvent(ch) | ||
} | ||
|
||
// NewSession provides a setup UserSession struct with sane defaults | ||
func NewSession(language string) (*UserSession, error) { | ||
if len(language) == 0 { | ||
language = "unset" | ||
} | ||
userSession := &UserSession{ | ||
HTTPClient: http.DefaultClient, | ||
ProtocolVersion: 1, | ||
Type: "event", | ||
TrackingID: trackingID, | ||
ApplicationName: applicationName, | ||
ApplicationVersion: version.BuildVersion(), | ||
AnonymizeIP: 1, | ||
Language: language, | ||
OS: runtime.GOOS, | ||
ARCH: runtime.GOARCH, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a thought this may mis-report I think it's set at build-time. One thing it won't do is to show armv6/7/8 differences. For that an OS library like pstate etc might help - although we'd need one that doesn't use CGO. |
||
EventCategory: "cli-success", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we get whether /.dockerenv exists? Tells you whether you're in a container or not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm adding this at the moment, just need to setup the GA custom dimension and Google Data Suite dashboard to validate it's getting pushed through. |
||
} | ||
|
||
uuid, err := getUUID() | ||
if err != nil { | ||
uuid, err = setUUID() | ||
if err != nil { | ||
return nil, fmt.Errorf("Unable to get or set Analytics Client ID") | ||
} | ||
} | ||
userSession.ClientID = uuid | ||
|
||
return userSession, nil | ||
} | ||
|
||
// PostEvent submits the generated event to Google Analytics, it is a | ||
// fire and forget process and ignores the response | ||
func (u UserSession) PostEvent(ch chan int) { | ||
v, _ := query.Values(u) | ||
req := &http.Request{ | ||
Method: http.MethodPost, | ||
Host: gaHost, | ||
URL: &url.URL{ | ||
Host: gaHost, | ||
Scheme: "https", | ||
Path: "/collect", | ||
RawQuery: v.Encode(), | ||
}, | ||
} | ||
u.HTTPClient.Do(req) | ||
ch <- 1 | ||
} | ||
|
||
// Disabled returns true if the opt-out envvar has been set | ||
// and false if it has not been set | ||
func Disabled() bool { | ||
val, ok := os.LookupEnv(disableEnvvar) | ||
if ok && val == "0" { | ||
return true | ||
} | ||
return false | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright (c) Alex Ellis 2017. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
package analytics | ||
|
||
import "net/http" | ||
|
||
// UserSession contains common Google Analytics event values and | ||
// the client used to submit the event. | ||
type UserSession struct { | ||
HTTPClient *http.Client `url:"-"` | ||
ProtocolVersion int `url:"v"` | ||
Type string `url:"t"` | ||
ClientID string `url:"cid"` | ||
TrackingID string `url:"tid"` | ||
ApplicationName string `url:"an"` | ||
ApplicationVersion string `url:"av"` | ||
AnonymizeIP int `url:"aip"` | ||
Language string `url:"cd1"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cd1? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Custom Dimension 1, its a Google Analytics term, the backend GA setup doc I shared should explain. |
||
OS string `url:"cd2"` | ||
ARCH string `url:"cd3"` | ||
EventCategory string `url:"ec,omitempty"` | ||
EventAction string `url:"ea,omitempty"` | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it easier to put this as an ENV just above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, could do.