mirror of
https://github.com/System-End/hackatime.git
synced 2026-04-19 22:15:14 +00:00
Add PgHero (#894)
This commit is contained in:
parent
797af036e9
commit
a2716fcdb0
11 changed files with 214 additions and 3 deletions
3
Gemfile
3
Gemfile
|
|
@ -161,3 +161,6 @@ gem "autotuner", "~> 1.0"
|
|||
gem "tailwindcss-ruby", "~> 4.1"
|
||||
|
||||
gem "tailwindcss-rails", "~> 4.2"
|
||||
|
||||
gem "pghero"
|
||||
gem "pg_query", ">= 2"
|
||||
|
|
|
|||
24
Gemfile.lock
24
Gemfile.lock
|
|
@ -228,6 +228,24 @@ GEM
|
|||
fugit (>= 1.11.0)
|
||||
railties (>= 6.1.0)
|
||||
thor (>= 1.0.0)
|
||||
google-protobuf (4.33.4)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.33.4-aarch64-linux-gnu)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.33.4-aarch64-linux-musl)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.33.4-arm64-darwin)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.33.4-x86_64-linux-gnu)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.33.4-x86_64-linux-musl)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
groupdate (6.7.0)
|
||||
activesupport (>= 7.1)
|
||||
hashie (5.1.0)
|
||||
|
|
@ -358,6 +376,10 @@ GEM
|
|||
pg (1.6.3-arm64-darwin)
|
||||
pg (1.6.3-x86_64-linux)
|
||||
pg (1.6.3-x86_64-linux-musl)
|
||||
pg_query (6.2.2)
|
||||
google-protobuf (>= 3.25.3)
|
||||
pghero (3.7.0)
|
||||
activerecord (>= 7.1)
|
||||
pp (0.6.3)
|
||||
prettyprint
|
||||
prettyprint (0.2.0)
|
||||
|
|
@ -655,6 +677,8 @@ DEPENDENCIES
|
|||
pagy (~> 43.2)
|
||||
paper_trail
|
||||
pg
|
||||
pg_query (>= 2)
|
||||
pghero
|
||||
propshaft
|
||||
public_activity
|
||||
puma (>= 5.0)
|
||||
|
|
|
|||
7
app/jobs/pghero/capture_query_stats_job.rb
Normal file
7
app/jobs/pghero/capture_query_stats_job.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
class Pghero::CaptureQueryStatsJob < ApplicationJob
|
||||
queue_as :literally_whenever
|
||||
|
||||
def perform
|
||||
PgHero.capture_query_stats
|
||||
end
|
||||
end
|
||||
7
app/jobs/pghero/capture_space_stats_job.rb
Normal file
7
app/jobs/pghero/capture_space_stats_job.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
class Pghero::CaptureSpaceStatsJob < ApplicationJob
|
||||
queue_as :literally_whenever
|
||||
|
||||
def perform
|
||||
PgHero.capture_space_stats
|
||||
end
|
||||
end
|
||||
10
app/jobs/pghero/clean_stats_job.rb
Normal file
10
app/jobs/pghero/clean_stats_job.rb
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
class Pghero::CleanStatsJob < ApplicationJob
|
||||
queue_as :literally_whenever
|
||||
|
||||
KEEP_DAYS = 30
|
||||
|
||||
def perform
|
||||
PgHero.clean_query_stats(before: KEEP_DAYS.days.ago)
|
||||
PgHero.clean_space_stats(before: KEEP_DAYS.days.ago)
|
||||
end
|
||||
end
|
||||
|
|
@ -171,6 +171,11 @@
|
|||
Feature Flags
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% superadmin_tool(nil, "div") do %>
|
||||
<%= link_to pghero_path, class: "block px-2 py-1 rounded-lg transition #{current_page?(pghero_path) ? 'bg-primary text-primary' : 'hover:bg-darkless'}", data: { action: "click->nav#clickLink" } do %>
|
||||
PgHero
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= render_activities(@activities) if defined?(@activities) %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -123,6 +123,23 @@ Rails.application.configure do
|
|||
cron: "0 2 * * *",
|
||||
class: "ProcessAccountDeletionsJob",
|
||||
description: "nuke accounts after 30 days"
|
||||
},
|
||||
|
||||
# PgHero stats
|
||||
pghero_capture_query_stats: {
|
||||
cron: "*/5 * * * *",
|
||||
class: "Pghero::CaptureQueryStatsJob",
|
||||
description: "Capture query stats for PgHero historical tracking"
|
||||
},
|
||||
pghero_capture_space_stats: {
|
||||
cron: "0 4 * * *",
|
||||
class: "Pghero::CaptureSpaceStatsJob",
|
||||
description: "Capture space stats for PgHero historical tracking (daily)"
|
||||
},
|
||||
pghero_clean_stats: {
|
||||
cron: "0 5 * * *",
|
||||
class: "Pghero::CleanStatsJob",
|
||||
description: "Clean PgHero stats older than 30 days"
|
||||
}
|
||||
# sync_stale_repo_metadata: {
|
||||
# cron: "0 4 * * *", # Daily at 4 AM
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ Rails.application.routes.draw do
|
|||
mount GoodJob::Engine => "good_job"
|
||||
mount AhoyCaptain::Engine => "/ahoy_captain"
|
||||
mount Flipper::UI.app(Flipper) => "flipper", as: :flipper
|
||||
mount PgHero::Engine => "pghero"
|
||||
|
||||
namespace :admin do
|
||||
resources :admin_users, only: [ :index, :update ] do
|
||||
|
|
|
|||
15
db/migrate/20260202210552_create_pghero_query_stats.rb
Normal file
15
db/migrate/20260202210552_create_pghero_query_stats.rb
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
class CreatePgheroQueryStats < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
create_table :pghero_query_stats do |t|
|
||||
t.text :database
|
||||
t.text :user
|
||||
t.text :query
|
||||
t.integer :query_hash, limit: 8
|
||||
t.float :total_time
|
||||
t.integer :calls, limit: 8
|
||||
t.timestamp :captured_at
|
||||
end
|
||||
|
||||
add_index :pghero_query_stats, [:database, :captured_at]
|
||||
end
|
||||
end
|
||||
13
db/migrate/20260202210555_create_pghero_space_stats.rb
Normal file
13
db/migrate/20260202210555_create_pghero_space_stats.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
class CreatePgheroSpaceStats < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
create_table :pghero_space_stats do |t|
|
||||
t.text :database
|
||||
t.text :schema
|
||||
t.text :relation
|
||||
t.integer :size, limit: 8
|
||||
t.timestamp :captured_at
|
||||
end
|
||||
|
||||
add_index :pghero_space_stats, [:database, :captured_at]
|
||||
end
|
||||
end
|
||||
115
db/schema.rb
generated
115
db/schema.rb
generated
|
|
@ -10,8 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.1].define(version: 2026_01_20_014910) do
|
||||
create_schema "pganalyze"
|
||||
ActiveRecord::Schema[8.1].define(version: 2026_02_02_210555) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_catalog.plpgsql"
|
||||
enable_extension "pg_stat_statements"
|
||||
|
|
@ -260,49 +259,128 @@ ActiveRecord::Schema[8.1].define(version: 2026_01_20_014910) do
|
|||
t.index ["scheduled_at"], name: "index_good_jobs_on_scheduled_at", where: "(finished_at IS NULL)"
|
||||
end
|
||||
|
||||
create_table "heartbeat_branches", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.string "name", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.index ["user_id", "name"], name: "index_heartbeat_branches_on_user_id_and_name", unique: true
|
||||
t.index ["user_id"], name: "index_heartbeat_branches_on_user_id"
|
||||
end
|
||||
|
||||
create_table "heartbeat_categories", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.string "name", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["name"], name: "index_heartbeat_categories_on_name", unique: true
|
||||
end
|
||||
|
||||
create_table "heartbeat_editors", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.string "name", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["name"], name: "index_heartbeat_editors_on_name", unique: true
|
||||
end
|
||||
|
||||
create_table "heartbeat_languages", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.string "name", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["name"], name: "index_heartbeat_languages_on_name", unique: true
|
||||
end
|
||||
|
||||
create_table "heartbeat_machines", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.string "name", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.index ["user_id", "name"], name: "index_heartbeat_machines_on_user_id_and_name", unique: true
|
||||
t.index ["user_id"], name: "index_heartbeat_machines_on_user_id"
|
||||
end
|
||||
|
||||
create_table "heartbeat_operating_systems", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.string "name", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["name"], name: "index_heartbeat_operating_systems_on_name", unique: true
|
||||
end
|
||||
|
||||
create_table "heartbeat_projects", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.string "name", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.index ["user_id", "name"], name: "index_heartbeat_projects_on_user_id_and_name", unique: true
|
||||
t.index ["user_id"], name: "index_heartbeat_projects_on_user_id"
|
||||
end
|
||||
|
||||
create_table "heartbeat_user_agents", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "value", null: false
|
||||
t.index ["value"], name: "index_heartbeat_user_agents_on_value", unique: true
|
||||
end
|
||||
|
||||
create_table "heartbeats", force: :cascade do |t|
|
||||
t.string "branch"
|
||||
t.bigint "branch_id"
|
||||
t.string "category"
|
||||
t.bigint "category_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "cursorpos"
|
||||
t.datetime "deleted_at"
|
||||
t.string "dependencies", default: [], array: true
|
||||
t.string "editor"
|
||||
t.bigint "editor_id"
|
||||
t.string "entity"
|
||||
t.text "fields_hash"
|
||||
t.inet "ip_address"
|
||||
t.boolean "is_write"
|
||||
t.string "language"
|
||||
t.bigint "language_id"
|
||||
t.integer "line_additions"
|
||||
t.integer "line_deletions"
|
||||
t.integer "lineno"
|
||||
t.integer "lines"
|
||||
t.string "machine"
|
||||
t.bigint "machine_id"
|
||||
t.string "operating_system"
|
||||
t.bigint "operating_system_id"
|
||||
t.string "project"
|
||||
t.bigint "project_id"
|
||||
t.integer "project_root_count"
|
||||
t.jsonb "raw_data"
|
||||
t.bigint "raw_heartbeat_upload_id"
|
||||
t.integer "source_type", null: false
|
||||
t.float "time", null: false
|
||||
t.string "type"
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "user_agent"
|
||||
t.bigint "user_agent_id"
|
||||
t.bigint "user_id", null: false
|
||||
t.integer "ysws_program", default: 0, null: false
|
||||
t.index ["branch_id"], name: "index_heartbeats_on_branch_id"
|
||||
t.index ["category", "time"], name: "index_heartbeats_on_category_and_time"
|
||||
t.index ["category_id"], name: "index_heartbeats_on_category_id"
|
||||
t.index ["editor_id"], name: "index_heartbeats_on_editor_id"
|
||||
t.index ["fields_hash"], name: "index_heartbeats_on_fields_hash_when_not_deleted", unique: true, where: "(deleted_at IS NULL)"
|
||||
t.index ["ip_address"], name: "index_heartbeats_on_ip_address"
|
||||
t.index ["language_id"], name: "index_heartbeats_on_language_id"
|
||||
t.index ["machine"], name: "index_heartbeats_on_machine"
|
||||
t.index ["machine_id"], name: "index_heartbeats_on_machine_id"
|
||||
t.index ["operating_system_id"], name: "index_heartbeats_on_operating_system_id"
|
||||
t.index ["project", "time"], name: "index_heartbeats_on_project_and_time"
|
||||
t.index ["project"], name: "index_heartbeats_on_project"
|
||||
t.index ["project_id"], name: "index_heartbeats_on_project_id"
|
||||
t.index ["raw_heartbeat_upload_id"], name: "index_heartbeats_on_raw_heartbeat_upload_id"
|
||||
t.index ["source_type", "time", "user_id", "project"], name: "index_heartbeats_on_source_type_time_user_project"
|
||||
t.index ["user_agent_id"], name: "index_heartbeats_on_user_agent_id"
|
||||
t.index ["user_id", "id"], name: "index_heartbeats_on_user_id_with_ip", order: { id: :desc }, where: "((ip_address IS NOT NULL) AND (deleted_at IS NULL))"
|
||||
t.index ["user_id", "project", "time"], name: "idx_heartbeats_user_project_time_stats", where: "((deleted_at IS NULL) AND (project IS NOT NULL))"
|
||||
t.index ["user_id", "time", "category"], name: "index_heartbeats_on_user_time_category"
|
||||
t.index ["user_id", "time", "language"], name: "idx_heartbeats_user_time_language_stats", where: "(deleted_at IS NULL)"
|
||||
t.index ["user_id", "time", "language_id"], name: "idx_heartbeats_user_time_language_id", where: "(deleted_at IS NULL)"
|
||||
t.index ["user_id", "time", "project"], name: "idx_heartbeats_user_time_project_stats", where: "(deleted_at IS NULL)"
|
||||
t.index ["user_id", "time", "project_id"], name: "idx_heartbeats_user_time_project_id", where: "(deleted_at IS NULL)"
|
||||
t.index ["user_id", "time"], name: "idx_heartbeats_user_time_active", where: "(deleted_at IS NULL)"
|
||||
t.index ["user_id"], name: "index_heartbeats_on_user_id"
|
||||
end
|
||||
|
|
@ -378,6 +456,26 @@ ActiveRecord::Schema[8.1].define(version: 2026_01_20_014910) do
|
|||
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
|
||||
end
|
||||
|
||||
create_table "pghero_query_stats", force: :cascade do |t|
|
||||
t.bigint "calls"
|
||||
t.datetime "captured_at", precision: nil
|
||||
t.text "database"
|
||||
t.text "query"
|
||||
t.bigint "query_hash"
|
||||
t.float "total_time"
|
||||
t.text "user"
|
||||
t.index ["database", "captured_at"], name: "index_pghero_query_stats_on_database_and_captured_at"
|
||||
end
|
||||
|
||||
create_table "pghero_space_stats", force: :cascade do |t|
|
||||
t.datetime "captured_at", precision: nil
|
||||
t.text "database"
|
||||
t.text "relation"
|
||||
t.text "schema"
|
||||
t.bigint "size"
|
||||
t.index ["database", "captured_at"], name: "index_pghero_space_stats_on_database_and_captured_at"
|
||||
end
|
||||
|
||||
create_table "project_labels", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.string "label"
|
||||
|
|
@ -578,6 +676,17 @@ ActiveRecord::Schema[8.1].define(version: 2026_01_20_014910) do
|
|||
add_foreign_key "deletion_requests", "users", column: "admin_approved_by_id"
|
||||
add_foreign_key "email_addresses", "users"
|
||||
add_foreign_key "email_verification_requests", "users"
|
||||
add_foreign_key "heartbeat_branches", "users"
|
||||
add_foreign_key "heartbeat_machines", "users"
|
||||
add_foreign_key "heartbeat_projects", "users"
|
||||
add_foreign_key "heartbeats", "heartbeat_branches", column: "branch_id"
|
||||
add_foreign_key "heartbeats", "heartbeat_categories", column: "category_id"
|
||||
add_foreign_key "heartbeats", "heartbeat_editors", column: "editor_id"
|
||||
add_foreign_key "heartbeats", "heartbeat_languages", column: "language_id"
|
||||
add_foreign_key "heartbeats", "heartbeat_machines", column: "machine_id"
|
||||
add_foreign_key "heartbeats", "heartbeat_operating_systems", column: "operating_system_id"
|
||||
add_foreign_key "heartbeats", "heartbeat_projects", column: "project_id"
|
||||
add_foreign_key "heartbeats", "heartbeat_user_agents", column: "user_agent_id"
|
||||
add_foreign_key "heartbeats", "raw_heartbeat_uploads"
|
||||
add_foreign_key "heartbeats", "users"
|
||||
add_foreign_key "leaderboard_entries", "leaderboards"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue