Module: Utils::Analytics Private

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

Overview

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

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_pathObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


351
352
353
# File 'Library/Homebrew/utils/analytics.rb', line 351

def analytics_path
  "analytics"
end

.cask_output(cask, args:) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


191
192
193
194
195
196
# File 'Library/Homebrew/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_pathObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


356
357
358
# File 'Library/Homebrew/utils/analytics.rb', line 356

def cask_path
  "cask"
end

.clear_os_prefix_ciObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


202
203
204
205
206
# File 'Library/Homebrew/utils/analytics.rb', line 202

def clear_os_prefix_ci
  return unless instance_variable_defined?(:@os_prefix_ci)

  remove_instance_variable(:@os_prefix_ci)
end

.config_delete(key) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


320
321
322
323
324
# File 'Library/Homebrew/utils/analytics.rb', line 320

def config_delete(key)
  HOMEBREW_REPOSITORY.cd do
    system "git", "config", "--unset-all", "homebrew.#{key}"
  end
end

.config_get(key) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


308
309
310
311
312
# File 'Library/Homebrew/utils/analytics.rb', line 308

def config_get(key)
  HOMEBREW_REPOSITORY.cd do
    Utils.popen_read("git", "config", "--get", "homebrew.#{key}").chomp
  end
end

.config_set(key, value) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


314
315
316
317
318
# File 'Library/Homebrew/utils/analytics.rb', line 314

def config_set(key, value)
  HOMEBREW_REPOSITORY.cd do
    safe_system "git", "config", "--replace-all", "homebrew.#{key}", value.to_s
  end
end

.config_true?(key) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)

304
305
306
# File 'Library/Homebrew/utils/analytics.rb', line 304

def config_true?(key)
  config_get(key) == "true"
end

.custom_prefix_labelObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


198
199
200
# File 'Library/Homebrew/utils/analytics.rb', line 198

def custom_prefix_label
  "custom-prefix"
end

.disable!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


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

def disable!
  config_set(:analyticsdisabled, true)
  regenerate_uuid!
end

.disabled?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)

85
86
87
88
89
# File 'Library/Homebrew/utils/analytics.rb', line 85

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

  config_true?(:analyticsdisabled)
end

.enable!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


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

def enable!
  config_set(:analyticsdisabled, false)
  messages_displayed!
end

.format_count(count) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


338
339
340
# File 'Library/Homebrew/utils/analytics.rb', line 338

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. You should avoid using this method if possible, as it may be removed or be changed in the future.


342
343
344
# File 'Library/Homebrew/utils/analytics.rb', line 342

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

.formula_output(f, args:) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


184
185
186
187
188
189
# File 'Library/Homebrew/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_pathObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


346
347
348
# File 'Library/Homebrew/utils/analytics.rb', line 346

def formula_path
  "formula"
end

.formulae_brew_sh_json(endpoint) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


326
327
328
329
330
331
332
333
334
335
336
# File 'Library/Homebrew/utils/analytics.rb', line 326

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

.generic_analytics_pathObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


354
355
356
# File 'Library/Homebrew/utils/analytics.rb', line 354

def analytics_path
  "analytics"
end

.generic_formula_pathObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


349
350
351
# File 'Library/Homebrew/utils/analytics.rb', line 349

def formula_path
  "formula"
end

.get_analytics(json, args:) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


155
156
157
158
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 'Library/Homebrew/utils/analytics.rb', line 155

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
        if args.days.present?
          next if args.days&.to_i != days
        end
        if args.category.present?
          next if args.category != category
        end

        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. You should avoid using this method if possible, as it may be removed or be changed in the future.


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

def messages_displayed!
  config_set(:analyticsmessage, true)
  config_set(:caskanalyticsmessage, true)
end

.messages_displayed?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)

81
82
83
# File 'Library/Homebrew/utils/analytics.rb', line 81

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

.no_message_output?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)

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

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. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)

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

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

.os_prefix_ciObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


208
209
210
211
212
213
214
215
# File 'Library/Homebrew/utils/analytics.rb', line 208

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

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


124
125
126
127
128
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
# File 'Library/Homebrew/utils/analytics.rb', line 124

def output(filter: nil, args:)
  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
    if filter.present?
      next if key != filter && !key.start_with?("#{filter} ")
    end
    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. You should avoid using this method if possible, as it may be removed or be changed in the future.


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

def regenerate_uuid!
  # it will be regenerated in next run unless disabled.
  config_delete(:analyticsuuid)
end

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


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

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 pid
  end
end

.report_build_error(exception) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


69
70
71
72
73
74
75
76
77
78
79
# File 'Library/Homebrew/utils/analytics.rb', line 69

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_prefix_ci, value = nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


61
62
63
64
65
66
67
# File 'Library/Homebrew/utils/analytics.rb', line 61

def report_event(category, action, label = os_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. You should avoid using this method if possible, as it may be removed or be changed in the future.


217
218
219
220
221
222
223
224
225
226
227
228
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
# File 'Library/Homebrew/utils/analytics.rb', line 217

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. You should avoid using this method if possible, as it may be removed or be changed in the future.


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

def uuid
  config_get(:analyticsuuid)
end