Class: ProjectStatistics

Inherits:
ApplicationRecord show all
Includes:
AfterCommitQueue, CounterAttribute
Defined in:
app/models/project_statistics.rb

Constant Summary collapse

COLUMNS_TO_REFRESH =
[:repository_size, :wiki_size, :lfs_objects_size, :commit_count, :snippets_size, :uploads_size, :container_registry_size].freeze
INCREMENTABLE_COLUMNS =
{
  build_artifacts_size: %i[storage_size],
  packages_size: %i[storage_size],
  pipeline_artifacts_size: %i[storage_size],
  snippets_size: %i[storage_size]
}.freeze
NAMESPACE_RELATABLE_COLUMNS =
[:repository_size, :wiki_size, :lfs_objects_size, :uploads_size].freeze

Constants included from CounterAttribute

CounterAttribute::LUA_STEAL_INCREMENT_SCRIPT, CounterAttribute::WORKER_DELAY, CounterAttribute::WORKER_LOCK_TTL

Constants included from Gitlab::ExclusiveLeaseHelpers

Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Class Method Summary collapse

Instance Method Summary collapse

Methods included from CounterAttribute

#clear_counter!, #counter_attribute_enabled?, #counter_flushed_key, #counter_key, #counter_lock_key, #delayed_increment_counter, #flush_increments_to_database!, #get_counter_value, #increment_counter

Methods included from AfterCommitQueue

#run_after_commit, #run_after_commit_or_now

Methods included from Gitlab::ExclusiveLeaseHelpers

#in_lock

Methods inherited from ApplicationRecord

cached_column_list, #create_or_load_association, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order

Methods included from SensitiveSerializableHash

#serializable_hash

Class Method Details

.columns_to_increment(key, amount) ⇒ Object


142
143
144
145
146
147
148
149
150
151
152
# File 'app/models/project_statistics.rb', line 142

def self.columns_to_increment(key, amount)
  updates = ["#{key} = COALESCE(#{key}, 0) + (#{amount})"]

  if (additional = INCREMENTABLE_COLUMNS[key])
    additional.each do |column|
      updates << "#{column} = COALESCE(#{column}, 0) + (#{amount})"
    end
  end

  update_all(updates.join(', '))
end

.increment_statistic(project, key, amount) ⇒ Object

Since this incremental update method does not call update_storage_size above, we have to update the storage_size here as additional column. Additional columns are updated depending on key => [columns], which allows to update statistics which are and also those which aren't included in storage_size or any other additional summary column in the future.

Raises:

  • (ArgumentError)

119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'app/models/project_statistics.rb', line 119

def self.increment_statistic(project, key, amount)
  raise ArgumentError, "Cannot increment attribute: #{key}" unless INCREMENTABLE_COLUMNS.key?(key)
  return if amount == 0

  project.statistics.try do |project_statistics|
    if project_statistics.counter_attribute_enabled?(key)
      statistics_to_increment = [key] + INCREMENTABLE_COLUMNS[key].to_a
      statistics_to_increment.each do |statistic|
        project_statistics.delayed_increment_counter(statistic, amount)
      end
    else
      legacy_increment_statistic(project, key, amount)
    end
  end
end

.legacy_increment_statistic(project, key, amount) ⇒ Object


135
136
137
138
139
140
# File 'app/models/project_statistics.rb', line 135

def self.legacy_increment_statistic(project, key, amount)
  where(project_id: project.id).columns_to_increment(key, amount)

  Namespaces::ScheduleAggregationWorker.perform_async( # rubocop: disable CodeReuse/Worker
    project.namespace_id)
end

Instance Method Details

#refresh!(only: []) ⇒ Object


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'app/models/project_statistics.rb', line 39

def refresh!(only: [])
  return if Gitlab::Database.read_only?

  COLUMNS_TO_REFRESH.each do |column, generator|
    if only.empty? || only.include?(column)
      public_send("update_#{column}") # rubocop:disable GitlabSecurity/PublicSend
    end
  end

  if only.empty? || only.any? { |column| NAMESPACE_RELATABLE_COLUMNS.include?(column) }
    schedule_namespace_aggregation_worker
  end

  save!
end

#snippets_sizeObject


97
98
99
# File 'app/models/project_statistics.rb', line 97

def snippets_size
  super.to_i
end

#total_repository_sizeObject


35
36
37
# File 'app/models/project_statistics.rb', line 35

def total_repository_size
  repository_size + lfs_objects_size
end

#update_commit_countObject


55
56
57
# File 'app/models/project_statistics.rb', line 55

def update_commit_count
  self.commit_count = project.repository.commit_count
end

#update_container_registry_sizeObject


79
80
81
82
83
# File 'app/models/project_statistics.rb', line 79

def update_container_registry_size
  return unless Feature.enabled?(:container_registry_project_statistics, project)

  self.container_registry_size = project.container_repositories_size || 0
end

#update_lfs_objects_sizeObject


71
72
73
# File 'app/models/project_statistics.rb', line 71

def update_lfs_objects_size
  self.lfs_objects_size = LfsObject.joins(:lfs_objects_projects).where(lfs_objects_projects: { project_id: project.id }).sum(:size)
end

#update_repository_sizeObject


59
60
61
# File 'app/models/project_statistics.rb', line 59

def update_repository_size
  self.repository_size = project.repository.size * 1.megabyte
end

#update_snippets_sizeObject


67
68
69
# File 'app/models/project_statistics.rb', line 67

def update_snippets_size
  self.snippets_size = project.snippets.with_statistics.sum(:repository_size)
end

#update_storage_sizeObject


101
102
103
104
105
106
107
108
109
110
111
112
# File 'app/models/project_statistics.rb', line 101

def update_storage_size
  storage_size = repository_size +
                 wiki_size +
                 lfs_objects_size +
                 build_artifacts_size +
                 packages_size +
                 snippets_size +
                 pipeline_artifacts_size +
                 uploads_size

  self.storage_size = storage_size
end

#update_uploads_sizeObject


75
76
77
# File 'app/models/project_statistics.rb', line 75

def update_uploads_size
  self.uploads_size = project.uploads.sum(:size)
end

#update_wiki_sizeObject


63
64
65
# File 'app/models/project_statistics.rb', line 63

def update_wiki_size
  self.wiki_size = project.wiki.repository.size * 1.megabyte
end

#wiki_sizeObject

`wiki_size` and `snippets_size` have no default value in the database and the column can be nil. This means that, when the columns were added, all rows had nil values on them. Therefore, any call to any of those methods will return nil instead of 0, because `default_value_for` works with new records, not existing ones.

These two methods provide consistency and avoid returning nil.


93
94
95
# File 'app/models/project_statistics.rb', line 93

def wiki_size
  super.to_i
end