Module: Utils::Analytics Private

Extended by:
Context, T::Sig
Defined in:
utils/analytics.rb,
extend/os/mac/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

.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.



206
207
208
209
210
211
212
213
214
215
216
# File 'utils/analytics.rb', line 206

def cask_output(cask, args:)
  return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api?

  json = Homebrew::API::Cask.fetch cask.token
  return if json.blank? || json["analytics"].blank?

  get_analytics(json, args: args)
rescue ArgumentError
  # Ignore failed API requests
  nil
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.



233
234
235
236
237
# File 'utils/analytics.rb', line 233

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)


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

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.



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

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)


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

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.



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

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.



340
341
342
# File 'utils/analytics.rb', line 340

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.



344
345
346
# File 'utils/analytics.rb', line 344

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.



194
195
196
197
198
199
200
201
202
203
204
# File 'utils/analytics.rb', line 194

def formula_output(f, args:)
  return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api?

  json = Homebrew::API::Formula.fetch f.name
  return if json.blank? || json["analytics"].blank?

  get_analytics(json, args: args)
rescue ArgumentError
  # Ignore failed API requests
  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.



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'utils/analytics.rb', line 169

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.



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

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)


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

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)


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

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)


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

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.



239
240
241
242
243
244
245
246
247
# File 'utils/analytics.rb', line 239

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.



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'utils/analytics.rb', line 134

def output(args:, filter: nil)
  days = args.days || "30"
  category = args.category || "install"
  begin
    json = Homebrew::API::Analytics.fetch category, days
  rescue ArgumentError
    # Ignore failed API requests
    return
  end
  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.



129
130
131
132
# File 'utils/analytics.rb', line 129

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.



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

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

  analytics_ids = ENV.fetch("HOMEBREW_ANALYTICS_IDS", "").split(",")
  analytics_ids.each do |analytics_id|
    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=#{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
  nil
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.



79
80
81
82
83
84
85
86
87
88
89
# File 'utils/analytics.rb', line 79

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.



71
72
73
74
75
76
77
# File 'utils/analytics.rb', line 71

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.



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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'utils/analytics.rb', line 249

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.



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

def uuid
  Homebrew::Settings.read :analyticsuuid
end