Module: Utils::Analytics Private

Extended by:
Context, T::Sig
Defined in:
utils/analytics.rb,
extend/os/mac/utils/analytics.rb,
extend/os/linux/utils/analytics.rb

Overview

This module is part of a private API. This module may only be used in the Homebrew/brew repository. Third parties should avoid using this module if possible, as it may be removed or changed without warning.

Helper module for fetching and reporting analytics data.

Class Method Summary collapse

Methods included from Context

current, current=, debug?, quiet?, verbose?, with_context

Class Method Details

.analytics_pathString Also known as: generic_analytics_path

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



16
17
18
19
20
# File 'extend/os/linux/utils/analytics.rb', line 16

def analytics_path
  return generic_analytics_path if Homebrew::EnvConfig.force_homebrew_on_linux?

  "analytics-linux"
end

.arch_labelString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



17
18
19
20
21
22
23
24
25
# File 'extend/os/mac/utils/analytics.rb', line 17

def arch_label
  if Hardware::CPU.arm?
    "ARM"
  elsif Hardware::CPU.in_rosetta2?
    "Rosetta"
  else
    ""
  end
end

.cask_output(cask, args:) ⇒ Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



191
192
193
194
195
196
# File 'utils/analytics.rb', line 191

def cask_output(cask, args:)
  json = formulae_brew_sh_json("#{cask_path}/#{cask}.json")
  return if json.blank? || json["analytics"].blank?

  get_analytics(json, args: args)
end

.cask_pathString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



353
354
355
# File 'utils/analytics.rb', line 353

def cask_path
  "cask"
end

.clear_os_arch_prefix_ciObject

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



213
214
215
216
217
# File 'utils/analytics.rb', line 213

def clear_os_arch_prefix_ci
  return unless instance_variable_defined?(:@os_arch_prefix_ci)

  remove_instance_variable(:@os_arch_prefix_ci)
end

.config_true?(key) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


316
317
318
# File 'utils/analytics.rb', line 316

def config_true?(key)
  Homebrew::Settings.read(key) == "true"
end

.custom_prefix_labelString Also known as: generic_custom_prefix_label

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



10
11
12
13
14
# File 'extend/os/mac/utils/analytics.rb', line 10

def custom_prefix_label
  return generic_custom_prefix_label if Hardware::CPU.arm?

  "non-/usr/local"
end

.disable!Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



119
120
121
122
# File 'utils/analytics.rb', line 119

def disable!
  Homebrew::Settings.write :analyticsdisabled, true
  regenerate_uuid!
end

.disabled?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


90
91
92
93
94
# File 'utils/analytics.rb', line 90

def disabled?
  return true if Homebrew::EnvConfig.no_analytics?

  config_true?(:analyticsdisabled)
end

.enable!Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



114
115
116
117
# File 'utils/analytics.rb', line 114

def enable!
  Homebrew::Settings.write :analyticsdisabled, false
  messages_displayed!
end

.format_count(count) ⇒ Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



332
333
334
# File 'utils/analytics.rb', line 332

def format_count(count)
  count.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
end

.format_percent(percent) ⇒ Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



336
337
338
# File 'utils/analytics.rb', line 336

def format_percent(percent)
  format("%<percent>.2f", percent: percent)
end

.formula_output(f, args:) ⇒ Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



184
185
186
187
188
189
# File 'utils/analytics.rb', line 184

def formula_output(f, args:)
  json = formulae_brew_sh_json("#{formula_path}/#{f}.json")
  return if json.blank? || json["analytics"].blank?

  get_analytics(json, args: args)
end

.formula_pathString Also known as: generic_formula_path

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



9
10
11
12
13
# File 'extend/os/linux/utils/analytics.rb', line 9

def formula_path
  return generic_formula_path if Homebrew::EnvConfig.force_homebrew_on_linux?

  "formula-linux"
end

.formulae_brew_sh_json(endpoint) ⇒ Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



320
321
322
323
324
325
326
327
328
329
330
# File 'utils/analytics.rb', line 320

def formulae_brew_sh_json(endpoint)
  return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api?

  output, = curl_output("--max-time", "5",
                        "https://formulae.brew.sh/api/#{endpoint}")
  return if output.blank?

  JSON.parse(output)
rescue JSON::ParserError
  nil
end

.get_analytics(json, args:) ⇒ Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'utils/analytics.rb', line 159

def get_analytics(json, args:)
  full_analytics = args.analytics? || verbose?

  ohai "Analytics"
  json["analytics"].each do |category, value|
    category = category.tr("_", "-")
    analytics = []

    value.each do |days, results|
      days = days.to_i
      if full_analytics
        next if args.days.present? && args.days&.to_i != days
        next if args.category.present? && args.category != category

        table_output(category, days, results)
      else
        total_count = results.values.inject("+")
        analytics << "#{number_readable(total_count)} (#{days} days)"
      end
    end

    puts "#{category}: #{analytics.join(", ")}" unless full_analytics
  end
end

.messages_displayed!Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



109
110
111
112
# File 'utils/analytics.rb', line 109

def messages_displayed!
  Homebrew::Settings.write :analyticsmessage, true
  Homebrew::Settings.write :caskanalyticsmessage, true
end

.messages_displayed?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


86
87
88
# File 'utils/analytics.rb', line 86

def messages_displayed?
  config_true?(:analyticsmessage) && config_true?(:caskanalyticsmessage)
end

.no_message_output?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


100
101
102
103
# File 'utils/analytics.rb', line 100

def no_message_output?
  # Used by Homebrew/install
  ENV["HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT"].present?
end

.not_this_run?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


96
97
98
# File 'utils/analytics.rb', line 96

def not_this_run?
  ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"].present?
end

.os_arch_prefix_ciObject

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



219
220
221
222
223
224
225
226
227
# File 'utils/analytics.rb', line 219

def os_arch_prefix_ci
  @os_arch_prefix_ci ||= begin
    os = OS_VERSION
    arch = ", #{arch_label}" if arch_label.present?
    prefix = ", #{custom_prefix_label}" unless Homebrew.default_prefix?
    ci = ", CI" if ENV["CI"]
    "#{os}#{arch}#{prefix}#{ci}"
  end
end

.output(args:, filter: nil) ⇒ Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'utils/analytics.rb', line 129

def output(args:, filter: nil)
  days = args.days || "30"
  category = args.category || "install"
  json = formulae_brew_sh_json("analytics/#{category}/#{days}d.json")
  return if json.blank? || json["items"].blank?

  os_version = category == "os-version"
  cask_install = category == "cask-install"
  results = {}
  json["items"].each do |item|
    key = if os_version
      item["os_version"]
    elsif cask_install
      item["cask"]
    else
      item["formula"]
    end
    next if filter.present? && key != filter && !key.start_with?("#{filter} ")

    results[key] = item["count"].tr(",", "").to_i
  end

  if filter.present? && results.blank?
    onoe "No results matching `#{filter}` found!"
    return
  end

  table_output(category, days, results, os_version: os_version, cask_install: cask_install)
end

.regenerate_uuid!Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



124
125
126
127
# File 'utils/analytics.rb', line 124

def regenerate_uuid!
  # it will be regenerated in next run unless disabled.
  Homebrew::Settings.delete :analyticsuuid
end

.report(type, metadata = {}) ⇒ Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



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
# File 'utils/analytics.rb', line 18

def report(type,  = {})
  return if not_this_run?
  return if disabled?

  args = []

  # do not load .curlrc unless requested (must be the first argument)
  args << "--disable" unless Homebrew::EnvConfig.curlrc?

  args += %W[
    --max-time 3
    --user-agent #{HOMEBREW_USER_AGENT_CURL}
    --data v=1
    --data aip=1
    --data t=#{type}
    --data tid=#{ENV["HOMEBREW_ANALYTICS_ID"]}
    --data cid=#{ENV["HOMEBREW_ANALYTICS_USER_UUID"]}
    --data an=#{HOMEBREW_PRODUCT}
    --data av=#{HOMEBREW_VERSION}
  ]
  .each do |key, value|
    next unless key
    next unless value

    key = ERB::Util.url_encode key
    value = ERB::Util.url_encode value
    args << "--data" << "#{key}=#{value}"
  end

  # Send analytics. Don't send or store any personally identifiable information.
  # https://docs.brew.sh/Analytics
  # https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide
  # https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters
  if ENV["HOMEBREW_ANALYTICS_DEBUG"]
    url = "https://www.google-analytics.com/debug/collect"
    puts "#{ENV["HOMEBREW_CURL"]} #{args.join(" ")} #{url}"
    puts Utils.popen_read ENV["HOMEBREW_CURL"], *args, url
  else
    pid = fork do
      exec ENV["HOMEBREW_CURL"],
           *args,
           "--silent", "--output", "/dev/null",
           "https://www.google-analytics.com/collect"
    end
    Process.detach T.must(pid)
  end
end

.report_build_error(exception) ⇒ Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



74
75
76
77
78
79
80
81
82
83
84
# File 'utils/analytics.rb', line 74

def report_build_error(exception)
  return unless exception.formula.tap
  return unless exception.formula.tap.installed?
  return if exception.formula.tap.private?

  action = exception.formula.full_name
  if (options = exception.options.to_a.map(&:to_s).join(" ").presence)
    action = "#{action} #{options}".strip
  end
  report_event("BuildError", action)
end

.report_event(category, action, label = os_arch_prefix_ci, value = nil) ⇒ Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



66
67
68
69
70
71
72
# File 'utils/analytics.rb', line 66

def report_event(category, action, label = os_arch_prefix_ci, value = nil)
  report(:event,
         ec: category,
         ea: action,
         el: label,
         ev: value)
end

.table_output(category, days, results, os_version: false, cask_install: false) ⇒ Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'utils/analytics.rb', line 229

def table_output(category, days, results, os_version: false, cask_install: false)
  oh1 "#{category} (#{days} days)"
  total_count = results.values.inject("+")
  formatted_total_count = format_count(total_count)
  formatted_total_percent = format_percent(100)

  index_header = "Index"
  count_header = "Count"
  percent_header = "Percent"
  name_with_options_header = if os_version
    "macOS Version"
  elsif cask_install
    "Token"
  else
    "Name (with options)"
  end

  total_index_footer = "Total"
  max_index_width = results.length.to_s.length
  index_width = [
    index_header.length,
    total_index_footer.length,
    max_index_width,
  ].max
  count_width = [
    count_header.length,
    formatted_total_count.length,
  ].max
  percent_width = [
    percent_header.length,
    formatted_total_percent.length,
  ].max
  name_with_options_width = Tty.width -
                            index_width -
                            count_width -
                            percent_width -
                            10 # spacing and lines

  formatted_index_header =
    format "%#{index_width}s", index_header
  formatted_name_with_options_header =
    format "%-#{name_with_options_width}s",
           name_with_options_header[0..name_with_options_width-1]
  formatted_count_header =
    format "%#{count_width}s", count_header
  formatted_percent_header =
    format "%#{percent_width}s", percent_header
  puts "#{formatted_index_header} | #{formatted_name_with_options_header} | "\
       "#{formatted_count_header} |  #{formatted_percent_header}"

  columns_line = "#{"-"*index_width}:|-#{"-"*name_with_options_width}-|-"\
                 "#{"-"*count_width}:|-#{"-"*percent_width}:"
  puts columns_line

  index = 0
  results.each do |name_with_options, count|
    index += 1
    formatted_index = format "%0#{max_index_width}d", index
    formatted_index = format "%-#{index_width}s", formatted_index
    formatted_name_with_options =
      format "%-#{name_with_options_width}s",
             name_with_options[0..name_with_options_width-1]
    formatted_count = format "%#{count_width}s", format_count(count)
    formatted_percent = if total_count.zero?
      format "%#{percent_width}s", format_percent(0)
    else
      format "%#{percent_width}s",
             format_percent((count.to_i * 100) / total_count.to_f)
    end
    puts "#{formatted_index} | #{formatted_name_with_options} | " \
         "#{formatted_count} | #{formatted_percent}%"
    next if index > 10
  end
  return unless results.length > 1

  formatted_total_footer =
    format "%-#{index_width}s", total_index_footer
  formatted_blank_footer =
    format "%-#{name_with_options_width}s", ""
  formatted_total_count_footer =
    format "%#{count_width}s", formatted_total_count
  formatted_total_percent_footer =
    format "%#{percent_width}s", formatted_total_percent
  puts "#{formatted_total_footer} | #{formatted_blank_footer} | "\
       "#{formatted_total_count_footer} | #{formatted_total_percent_footer}%"
end

.uuidObject

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



105
106
107
# File 'utils/analytics.rb', line 105

def uuid
  Homebrew::Settings.read :analyticsuuid
end