Implements Web IDE commits counter in Redis

This makes a temporary implementation of the
Web IDE commits counter using Redis while
https://gitlab.com/gitlab-org/gitlab-ce/issues/52096
is being discussed further for a more generic
approach to counters
This commit is contained in:
Tiago Botelho 2018-10-02 16:33:43 +01:00
parent 9b2e17ac71
commit 1e662293e8
12 changed files with 43 additions and 133 deletions

View File

@ -1,42 +0,0 @@
# frozen_string_literal: true
class UsageCounters < ActiveRecord::Base
RECORD_LIMIT = 1.freeze
BY = 1.freeze
BLACKLIST_ATTRIBUTES = %w(id created_at updated_at).freeze
validate :ensure_only_one, on: :create
default_value_for :web_ide_commits, 0
# This method supports concurrency so that several
# requests are able to increment the counter without
# us having inconsistent data
def increment_counters(attrs)
# We want to be able to use the service to increment
# both a single and multiple counters
attrs = Array(attrs)
attrs_with_by =
attrs.each_with_object({}) do |attr, hsh|
hsh[attr] = BY
end
self.class.update_counters(id, attrs_with_by)
end
# Every attribute in this table except the blacklisted
# attributes is a counter
def totals
attributes.except(*BLACKLIST_ATTRIBUTES).symbolize_keys
end
private
# We only want one UsageCounters per instance
def ensure_only_one
return unless UsageCounters.count >= RECORD_LIMIT
errors.add(:base, 'There can only be one usage counters record per instance')
end
end

View File

@ -1,13 +0,0 @@
# frozen_string_literal: true
class CreateUsageCounters < ActiveRecord::Migration
DOWNTIME = false
def change
create_table :usage_counters do |t|
t.integer :web_ide_commits
t.timestamps_with_timezone null: false
end
end
end

View File

@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180929102611) do ActiveRecord::Schema.define(version: 20180917172041) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -2080,12 +2080,6 @@ ActiveRecord::Schema.define(version: 20180929102611) do
add_index "uploads", ["model_id", "model_type"], name: "index_uploads_on_model_id_and_model_type", using: :btree add_index "uploads", ["model_id", "model_type"], name: "index_uploads_on_model_id_and_model_type", using: :btree
add_index "uploads", ["uploader", "path"], name: "index_uploads_on_uploader_and_path", using: :btree add_index "uploads", ["uploader", "path"], name: "index_uploads_on_uploader_and_path", using: :btree
create_table "usage_counters", force: :cascade do |t|
t.integer "web_ide_commits"
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
end
create_table "user_agent_details", force: :cascade do |t| create_table "user_agent_details", force: :cascade do |t|
t.string "user_agent", null: false t.string "user_agent", null: false
t.string "ip_address", null: false t.string "ip_address", null: false

View File

@ -109,7 +109,7 @@ module API
if result[:status] == :success if result[:status] == :success
commit_detail = user_project.repository.commit(result[:result]) commit_detail = user_project.repository.commit(result[:result])
UsageCounters.first_or_create.increment_counters(:web_ide_commits) if find_user_from_warden Gitlab::WebIdeCommitsCounter.increment if find_user_from_warden
present commit_detail, with: Entities::CommitDetail present commit_detail, with: Entities::CommitDetail
else else

View File

@ -108,7 +108,9 @@ module Gitlab
end end
def usage_counters def usage_counters
UsageCounters.first_or_create.totals {
web_ide_commits: Gitlab::WebIdeCommitsCounter.total_count
}
end end
def components_usage_data def components_usage_data

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Gitlab
module WebIdeCommitsCounter
WEB_IDE_COMMITS_KEY = "WEB_IDE_COMMITS_COUNT".freeze
class << self
def increment
Gitlab::Redis::SharedState.with { |redis| redis.incr(WEB_IDE_COMMITS_KEY) }
end
def total_count
Gitlab::Redis::SharedState.with { |redis| redis.get(WEB_IDE_COMMITS_KEY).to_i }
end
end
end
end

View File

@ -1,6 +0,0 @@
# frozen_string_literal: true
FactoryBot.define do
factory :usage_counters, class: 'UsageCounters' do
end
end

View File

@ -330,6 +330,3 @@ resource_label_events:
- merge_request - merge_request
- epic - epic
- label - label
usage_counters:
- project
- web_ide_commits

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::WebIdeCommitsCounter, :clean_gitlab_redis_shared_state do
describe '.increment' do
it 'increments the web ide commits counter by 1' do
expect do
described_class.increment
end.to change { described_class.total_count }.from(0).to(1)
end
end
describe '.total_count' do
it 'returns the total amount of web ide commits' do
expect(described_class.total_count).to eq(0)
end
end
end

View File

@ -1,31 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
describe UsageCounters do
let!(:usage_counters) { create(:usage_counters) }
describe 'maximum number of records' do
it 'allows for one single record to be created' do
expect do
described_class.create!
end.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: There can only be one usage counters record per instance')
end
end
describe '#totals' do
subject { usage_counters.totals }
it 'returns counters' do
is_expected.to include(web_ide_commits: 0)
end
end
describe '#increment_counters' do
it 'increments specified counters by 1' do
expect do
usage_counters.increment_counters(:web_ide_commits)
end.to change { usage_counters.reload.web_ide_commits }.from(0).to(1)
end
end
end

View File

@ -279,9 +279,9 @@ describe API::Commits do
end end
it 'does not increment the usage counters using access token authentication' do it 'does not increment the usage counters using access token authentication' do
post api(url, user), valid_c_params expect(::Gitlab::WebIdeCommitsCounter).not_to receive(:increment)
expect_any_instance_of(::UsageCounters).not_to receive(:increment_counters) post api(url, user), valid_c_params
end end
it 'a new file in project repo' do it 'a new file in project repo' do

View File

@ -1,27 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
describe Projects::UsageCountersIncrementService do
let(:project) { create(:usage_counters) }
subject(:service) { described_class.new(project) }
context '#execute' do
context 'when single attribute is passed' do
it 'increments attribute' do
expect do
service.execute(:web_ide_commits)
end.to change { project.usage_counters.reload.web_ide_commits }.from(0).to(1)
end
end
context 'when array is passed' do
it 'increments specified attributes' do
expect do
service.execute(%i(web_ide_commits))
end.to change { project.usage_counters.reload.web_ide_commits }.from(0).to(1)
end
end
end
end