Merge pull request #14583 from SMillerDev/fix/analytics/improve_keys

fix: add better keys and fuller values to influxDB analytics
This commit is contained in:
Mike McQuaid 2023-02-15 17:20:51 +00:00 committed by GitHub
commit ba06013f72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 208 additions and 113 deletions

View File

@ -41,7 +41,7 @@ Metrics/PerceivedComplexity:
Metrics/MethodLength:
Max: 232
Metrics/ModuleLength:
Max: 475
Max: 480
Exclude:
# TODO: extract more of the bottling logic
- "dev-cmd/bottle.rb"

View File

@ -651,6 +651,7 @@ export HOMEBREW_GIT
export HOMEBREW_GIT_WARNING
export HOMEBREW_MINIMUM_GIT_VERSION
export HOMEBREW_LINUX_MINIMUM_GLIBC_VERSION
export HOMEBREW_PHYSICAL_PROCESSOR
export HOMEBREW_PROCESSOR
export HOMEBREW_PRODUCT
export HOMEBREW_OS_VERSION

View File

@ -111,7 +111,7 @@ module Cask
install_artifacts
if @cask.tap&.should_report_analytics?
::Utils::Analytics.report_event("cask_install", @cask.token, on_request: true)
::Utils::Analytics.report_event(:cask_install, @cask.token, on_request: true)
end
purge_backed_up_versioned_files

View File

@ -297,6 +297,11 @@ module Homebrew
description: "If set, do not print any hints about changing Homebrew's behaviour with environment variables.",
boolean: true,
},
HOMEBREW_NO_GOOGLE_ANALYTICS: {
description: "If set, do not send analytics to Google Analytics but allow sending to Homebrew's InfluxDB " \
"analytics server. For more information, see: <https://docs.brew.sh/Analytics>",
boolean: true,
},
HOMEBREW_NO_GITHUB_API: {
description: "If set, do not use the GitHub API, e.g. for searches or fetching relevant issues " \
"after a failed install.",

View File

@ -6,22 +6,18 @@ module Utils
class << self
extend T::Sig
sig { params(verbose: T::Boolean).returns(String) }
def custom_prefix_label(verbose: false)
return generic_custom_prefix_label if Hardware::CPU.arm?
sig { returns(String) }
def custom_prefix_label_google
return generic_custom_prefix_label_google if Hardware::CPU.arm?
"non-/usr/local"
end
sig { params(verbose: T::Boolean).returns(String) }
def arch_label(verbose: false)
if Hardware::CPU.arm?
"ARM"
elsif Hardware::CPU.in_rosetta2?
"Rosetta"
else
""
end
sig { returns(String) }
def arch_label_google
return "Rosetta" if Hardware::CPU.in_rosetta2?
generic_arch_label_google
end
end
end

View File

@ -416,7 +416,7 @@ class FormulaInstaller
if formula.tap&.should_report_analytics?
action = "#{formula.full_name} #{options}".strip
Utils::Analytics.report_event("install", action, on_request: installed_on_request?)
Utils::Analytics.report_event(:formula_install, action, on_request: installed_on_request?)
end
self.class.attempted << formula

View File

@ -48,6 +48,7 @@ HOMEBREW_WWW = "https://brew.sh"
HOMEBREW_DOCS_WWW = "https://docs.brew.sh"
HOMEBREW_SYSTEM = ENV.fetch("HOMEBREW_SYSTEM").freeze
HOMEBREW_PROCESSOR = ENV.fetch("HOMEBREW_PROCESSOR").freeze
HOMEBREW_PHYSICAL_PROCESSOR = ENV.fetch("HOMEBREW_PHYSICAL_PROCESSOR").freeze
HOMEBREW_BREWED_CURL_PATH = Pathname(ENV.fetch("HOMEBREW_BREWED_CURL_PATH")).freeze
HOMEBREW_USER_AGENT_CURL = ENV.fetch("HOMEBREW_USER_AGENT_CURL").freeze

View File

@ -5,49 +5,93 @@ require "utils/analytics"
require "formula_installer"
describe Utils::Analytics do
describe "::os_arch_prefix_ci" do
context "when os_arch_prefix_ci is not set" do
before do
described_class.clear_os_arch_prefix_ci
end
describe "::label_google" do
before do
described_class.clear_additional_tags_cache
end
let(:ci) { ", CI" if ENV["CI"] }
let(:ci) { ", CI" if ENV["CI"] }
it "returns OS_VERSION and prefix when HOMEBREW_PREFIX is a custom prefix on intel" do
allow(Hardware::CPU).to receive(:type).and_return(:intel)
allow(Hardware::CPU).to receive(:in_rosetta2?).and_return(false)
allow(Homebrew).to receive(:default_prefix?).and_return(false)
expect(described_class.os_arch_prefix_ci).to have_key(:prefix)
expect(described_class.os_arch_prefix_ci[:prefix]).to eq described_class.custom_prefix_label
end
it "returns OS_VERSION and prefix when HOMEBREW_PREFIX is a custom prefix on intel" do
allow(Hardware::CPU).to receive(:type).and_return(:intel)
allow(Hardware::CPU).to receive(:in_rosetta2?).and_return(false)
allow(Homebrew).to receive(:default_prefix?).and_return(false)
expect(described_class.label_google).to include described_class.custom_prefix_label_google
end
it "returns OS_VERSION, ARM and prefix when HOMEBREW_PREFIX is a custom prefix on arm" do
allow(Hardware::CPU).to receive(:type).and_return(:arm)
allow(Hardware::CPU).to receive(:in_rosetta2?).and_return(false)
allow(Homebrew).to receive(:default_prefix?).and_return(false)
expect(described_class.os_arch_prefix_ci).to have_key(:arch)
expect(described_class.os_arch_prefix_ci[:arch]).to eq described_class.arch_label
expect(described_class.os_arch_prefix_ci).to have_key(:prefix)
expect(described_class.os_arch_prefix_ci[:prefix]).to eq described_class.custom_prefix_label
end
it "returns OS_VERSION, ARM and prefix when HOMEBREW_PREFIX is a custom prefix on arm" do
allow(Hardware::CPU).to receive(:type).and_return(:arm)
allow(Hardware::CPU).to receive(:in_rosetta2?).and_return(false)
allow(Homebrew).to receive(:default_prefix?).and_return(false)
expect(described_class.label_google).to include described_class.arch_label_google
expect(described_class.label_google).to include described_class.custom_prefix_label_google
end
it "returns OS_VERSION, Rosetta and prefix when HOMEBREW_PREFIX is a custom prefix on Rosetta", :needs_macos do
allow(Hardware::CPU).to receive(:type).and_return(:intel)
allow(Hardware::CPU).to receive(:in_rosetta2?).and_return(true)
allow(Homebrew).to receive(:default_prefix?).and_return(false)
expect(described_class.os_arch_prefix_ci).to have_key(:prefix)
expect(described_class.os_arch_prefix_ci[:prefix]).to eq described_class.custom_prefix_label
end
it "returns OS_VERSION, Rosetta and prefix when HOMEBREW_PREFIX is a custom prefix on Rosetta", :needs_macos do
allow(Hardware::CPU).to receive(:type).and_return(:intel)
allow(Hardware::CPU).to receive(:in_rosetta2?).and_return(true)
allow(Homebrew).to receive(:default_prefix?).and_return(false)
expect(described_class.label_google).to include described_class.custom_prefix_label_google
end
it "does not include prefix when HOMEBREW_PREFIX is the default prefix" do
allow(Homebrew).to receive(:default_prefix?).and_return(true)
expect(described_class.os_arch_prefix_ci).not_to have_key(:prefix)
end
it "does not include prefix when HOMEBREW_PREFIX is the default prefix" do
allow(Homebrew).to receive(:default_prefix?).and_return(true)
expect(described_class.label_google).not_to include HOMEBREW_PREFIX.to_s
end
it "includes CI when ENV['CI'] is set" do
ENV["CI"] = "true"
expect(described_class.os_arch_prefix_ci).to have_key(:ci)
end
it "includes CI when ENV['CI'] is set" do
ENV["CI"] = "1"
expect(described_class.label_google).to include "CI"
end
end
describe "::additional_tags_influx" do
before do
described_class.clear_additional_tags_cache
end
let(:ci) { ", CI" if ENV["CI"] }
it "returns OS_VERSION and prefix when HOMEBREW_PREFIX is a custom prefix on intel" do
allow(Hardware::CPU).to receive(:type).and_return(:intel)
allow(Hardware::CPU).to receive(:in_rosetta2?).and_return(false)
allow(Homebrew).to receive(:default_prefix?).and_return(false)
expect(described_class.additional_tags_influx).to have_key(:prefix)
expect(described_class.additional_tags_influx[:prefix]).to eq "custom-prefix"
end
it "returns OS_VERSION, ARM and prefix when HOMEBREW_PREFIX is a custom prefix on arm" do
allow(Hardware::CPU).to receive(:type).and_return(:arm)
allow(Hardware::CPU).to receive(:in_rosetta2?).and_return(false)
allow(Homebrew).to receive(:default_prefix?).and_return(false)
expect(described_class.additional_tags_influx).to have_key(:arch)
expect(described_class.additional_tags_influx[:arch]).to eq HOMEBREW_PHYSICAL_PROCESSOR
expect(described_class.additional_tags_influx).to have_key(:prefix)
expect(described_class.additional_tags_influx[:prefix]).to eq "custom-prefix"
end
it "returns OS_VERSION, Rosetta and prefix when HOMEBREW_PREFIX is a custom prefix on Rosetta", :needs_macos do
allow(Hardware::CPU).to receive(:type).and_return(:intel)
allow(Hardware::CPU).to receive(:in_rosetta2?).and_return(true)
allow(Homebrew).to receive(:default_prefix?).and_return(false)
expect(described_class.additional_tags_influx).to have_key(:prefix)
expect(described_class.additional_tags_influx[:prefix]).to eq "custom-prefix"
end
it "does not include prefix when HOMEBREW_PREFIX is the default prefix" do
allow(Homebrew).to receive(:default_prefix?).and_return(true)
expect(described_class.additional_tags_influx).to have_key(:prefix)
expect(described_class.additional_tags_influx[:prefix]).to eq HOMEBREW_PREFIX.to_s
end
it "includes CI when ENV['CI'] is set" do
ENV["CI"] = "1"
expect(described_class.additional_tags_influx).to have_key(:ci)
end
it "includes developer when ENV['HOMEBREW_DEVELOPER'] is set" do
ENV["HOMEBREW_DEVELOPER"] = "1"
expect(described_class.additional_tags_influx).to have_key(:developer)
end
end
@ -61,14 +105,14 @@ describe Utils::Analytics do
ENV["HOMEBREW_NO_ANALYTICS"] = "true"
expect(described_class).not_to receive(:report_google)
expect(described_class).not_to receive(:report_influx)
described_class.report_event("install", action)
described_class.report_event(:install, action)
end
it "returns nil when HOMEBREW_NO_ANALYTICS_THIS_RUN is true" do
ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] = "true"
expect(described_class).not_to receive(:report_google)
expect(described_class).not_to receive(:report_influx)
described_class.report_event("install", action)
described_class.report_event(:install, action)
end
it "returns nil when HOMEBREW_ANALYTICS_DEBUG is true" do
@ -78,13 +122,14 @@ describe Utils::Analytics do
expect(described_class).to receive(:report_google)
expect(described_class).to receive(:report_influx)
described_class.report_event("install", action)
described_class.report_event(:install, action)
end
end
it "passes to the influxdb and google methods" do
ENV.delete("HOMEBREW_NO_ANALYTICS_THIS_RUN")
ENV.delete("HOMEBREW_NO_ANALYTICS")
ENV.delete("HOMEBREW_DEVELOPER")
ENV["HOMEBREW_ANALYTICS_DEBUG"] = "true"
expect(described_class).to receive(:report_google).with(:event, hash_including(ea: action)).once
expect(described_class).to receive(:report_influx).with(:install, "formula_name --head", false,
@ -95,6 +140,7 @@ describe Utils::Analytics do
it "sends to google twice on request" do
ENV.delete("HOMEBREW_NO_ANALYTICS_THIS_RUN")
ENV.delete("HOMEBREW_NO_ANALYTICS")
ENV.delete("HOMEBREW_DEVELOPER")
ENV["HOMEBREW_ANALYTICS_DEBUG"] = "true"
expect(described_class).to receive(:report_google).with(:event, hash_including(ea: action, ec: :install)).once
expect(described_class).to receive(:report_google).with(:event,
@ -117,6 +163,7 @@ describe Utils::Analytics do
ENV.delete("HOMEBREW_NO_ANALYTICS")
ENV["HOMEBREW_ANALYTICS_DEBUG"] = "true"
ENV["HOMEBREW_ANALYTICS_ENABLE_INFLUX"] = "true"
ENV["HOMEBREW_DEVELOPER"] = "1"
expect(described_class).to receive(:deferred_curl).once
described_class.report_influx(:install, action, true, developer: true, CI: true)
end

View File

@ -20,15 +20,8 @@ module Utils
INFLUX_TOKEN = "y2JZsgE7glWT9V-S-nElETLp8oyH9PGh9JVa-kCdOdp7mEHIOws4BtdjsIe3HHpCBty7IQHLnmh0prqK2vBj9A=="
INFLUX_HOST = "europe-west1-1.gcp.cloud2.influxdata.com"
sig { params(type: T.any(String, Symbol), metadata: T::Hash[Symbol, T.untyped]).void }
sig { params(type: Symbol, metadata: T::Hash[Symbol, T.untyped]).void }
def report_google(type, metadata = {})
os = metadata[:el][:os]
arch = ", #{metadata[:el][:arch]}" if metadata[:el][:arch].present?
prefix = ", #{metadata[:el][:prefix]}" if metadata[:el][:prefix].present?
ci = ", CI" if metadata[:el][:CI] == true
metadata[:el] = "#{os}#{arch}#{prefix}#{ci}"
analytics_ids = ENV.fetch("HOMEBREW_ANALYTICS_IDS", "").split(",")
analytics_ids.each do |analytics_id|
args = []
@ -79,14 +72,14 @@ module Utils
end
sig {
params(category: T.any(String, Symbol), action: T.any(String, Symbol), on_request: T::Boolean,
params(measurement: Symbol, package_and_options: String, on_request: T::Boolean,
additional_tags: T::Hash[Symbol, T.untyped]).void
}
def report_influx(category, action, on_request, additional_tags = {})
def report_influx(measurement, package_and_options, on_request, additional_tags = {})
return unless ENV["HOMEBREW_ANALYTICS_ENABLE_INFLUX"]
# Append general information to device information
tags = additional_tags.merge(action: action, on_request: !on_request.nil?)
tags = additional_tags.merge(package_and_options: package_and_options, on_request: !on_request.nil?)
.compact_blank
.map { |k, v| "#{k}=#{v.to_s.sub(" ", "\\ ")}" } # convert to key/value parameters
.join(",")
@ -96,7 +89,7 @@ module Utils
"--header", "Content-Type: text/plain; charset=utf-8",
"--header", "Accept: application/json",
"--header", "Authorization: Token #{INFLUX_TOKEN}",
"--data-raw", "#{category},#{tags} count=1i #{Time.now.to_i}"
"--data-raw", "#{measurement},#{tags} count=1i #{Time.now.to_i}"
]
url = "https://#{INFLUX_HOST}/api/v2/write?bucket=#{INFLUX_BUCKET}&precision=s"
@ -117,40 +110,76 @@ module Utils
end
end
sig { params(category: T.any(String, Symbol), action: String, on_request: T::Boolean).void }
def report_event(category, action, on_request: false)
return if not_this_run?
return if disabled?
sig { params(measurement: Symbol, package_and_options: String, on_request: T::Boolean).void }
def report_event(measurement, package_and_options, on_request: false)
report_google_event(measurement, package_and_options, on_request: on_request)
report_influx_event(measurement, package_and_options, on_request: on_request)
end
google_label = os_arch_prefix_ci(verbose: false)
sig { params(category: Symbol, action: String, on_request: T::Boolean).void }
def report_google_event(category, action, on_request: false)
return if not_this_run? || disabled? || Homebrew::EnvConfig.no_google_analytics?
category = "install" if category == :formula_install
report_google(:event,
ec: category,
ea: action,
el: google_label,
el: label_google,
ev: nil)
if on_request
report_google(:event,
ec: :install_on_request,
ea: action,
el: google_label,
ev: nil)
end
return unless on_request
influx_additional_data = os_arch_prefix_ci(verbose: true)
report_influx(category, action, on_request, influx_additional_data)
report_google(:event,
ec: :install_on_request,
ea: action,
el: label_google,
ev: nil)
end
sig { params(measurement: Symbol, package_and_options: String, on_request: T::Boolean).void }
def report_influx_event(measurement, package_and_options, on_request: false)
return if not_this_run? || disabled?
report_influx(measurement, package_and_options, on_request, additional_tags_influx)
end
sig { params(exception: Exception).void }
def report_build_error(exception)
report_google_build_error(exception)
report_influx_error(exception)
end
sig { params(exception: Exception).void }
def report_google_build_error(exception)
return if not_this_run? || disabled?
return unless exception.formula.tap
return unless exception.formula.tap.should_report_analytics?
action = exception.formula.full_name
if (options = exception.options.to_a.map(&:to_s).join(" ").presence)
action = "#{action} #{options}".strip
formula_full_name = exception.formula.full_name
package_and_options = if (options = exception.options.to_a.map(&:to_s).join(" ").presence)
"#{formula_full_name} #{options}".strip
else
formula_full_name
end
report_event("BuildError", action)
report_event("BuildError", package_and_options)
end
sig { params(exception: Exception).void }
def report_influx_error(exception)
return if not_this_run? || disabled?
return unless exception.formula.tap
return unless exception.formula.tap.should_report_analytics?
formula_full_name = exception.formula.full_name
package_and_options = if (options = exception.options.to_a.map(&:to_s).join(" ").presence)
"#{formula_full_name} #{options}".strip
else
formula_full_name
end
report_event(:build_error, package_and_options)
end
def messages_displayed?
@ -280,45 +309,55 @@ module Utils
nil
end
sig { params(verbose: T::Boolean).returns(String) }
def custom_prefix_label(verbose: false)
sig { returns(String) }
def custom_prefix_label_google
"custom-prefix"
end
alias generic_custom_prefix_label custom_prefix_label
alias generic_custom_prefix_label_google custom_prefix_label_google
sig { params(verbose: T::Boolean).returns(String) }
def arch_label(verbose: false)
sig { returns(String) }
def arch_label_google
if Hardware::CPU.arm?
"ARM"
else
""
end
end
alias generic_arch_label_google arch_label_google
def clear_os_arch_prefix_ci
return unless instance_variable_defined?(:@os_arch_prefix_ci)
remove_instance_variable(:@os_arch_prefix_ci)
def clear_additional_tags_cache
remove_instance_variable(:@label_google) if instance_variable_defined?(:@label_google)
remove_instance_variable(:@additional_tags_influx) if instance_variable_defined?(:@additional_tags_influx)
end
sig { params(verbose: T::Boolean).returns(T::Hash[Symbol, String]) }
def os_arch_prefix_ci(verbose: false)
@os_arch_prefix_ci ||= begin
data = {
os: OS_VERSION,
developer: Homebrew::EnvConfig.developer?,
version: HOMEBREW_VERSION,
system: HOMEBREW_SYSTEM,
ci: ENV["CI"].present?,
arch: arch_label(verbose: verbose),
prefix: custom_prefix_label(verbose: verbose),
}
unless verbose
data.delete(:arch) if data[:arch].blank?
data.delete(:prefix) if Homebrew.default_prefix?
end
sig { returns(String) }
def label_google
@label_google ||= begin
os = OS_VERSION
arch = ", #{arch_label_google}" if arch_label_google.present?
prefix = ", #{custom_prefix_label_google}" unless Homebrew.default_prefix?
ci = ", CI" if ENV["CI"]
"#{os}#{arch}#{prefix}#{ci}"
end
end
data
sig { returns(T::Hash[Symbol, String]) }
def additional_tags_influx
@additional_tags_influx ||= begin
version = HOMEBREW_VERSION.match(/^[\d.]+/)[0]
version = "#{version}-dev" if HOMEBREW_VERSION.include?("-")
prefix = Homebrew.default_prefix? ? HOMEBREW_PREFIX.to_s : "custom-prefix"
{
version: version,
prefix: prefix,
default_prefix: Homebrew.default_prefix?,
ci: ENV["CI"].present?,
developer: Homebrew::EnvConfig.developer?,
arch: HOMEBREW_PHYSICAL_PROCESSOR,
os: HOMEBREW_SYSTEM,
os_name_and_version: OS_VERSION,
}
end
end

View File

@ -1,6 +1,6 @@
# Anonymous Aggregate User Behaviour Analytics
Homebrew gathers anonymous aggregate user behaviour analytics using Google Analytics. You will be notified the first time you run `brew update` or install Homebrew. Analytics are not enabled until after this notice is shown, to ensure that you can [opt out](Analytics.md#opting-out) without ever sending analytics data.
Homebrew gathers anonymous aggregate user behaviour analytics using Google Analytics (until our in-progress migration to our own InfluxDB). You will be notified the first time you run `brew update` or install Homebrew. Analytics are not enabled until after this notice is shown, to ensure that you can [opt out](Analytics.md#opting-out) without ever sending analytics data.
## Why?
@ -61,6 +61,12 @@ Homebrew analytics helps us maintainers and leaving it on is appreciated. Howeve
export HOMEBREW_NO_ANALYTICS=1
```
If you are fine with analytics being sent to Homebrew's InfluxDB but not to Google Analytics, you can set:
```sh
export HOMEBREW_NO_GOOGLE_ANALYTICS=1
```
Alternatively, this will prevent analytics from ever being sent:
```sh