rails-profiler 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e968972f60bc4764a4b61648179a5bf618f2d1f2a68b9d43f724c4e14f3c2794
4
- data.tar.gz: 70536b9f1ed2c5d04b698657ae9f948abad17cbc9fa202125311092b77d94005
3
+ metadata.gz: 44b1fd924fe2eb7f0483bc36f0e3956e81535da3ce39e60f6e29c091194f22ce
4
+ data.tar.gz: c840abbc0813c4e8c9516c507138e4ead25e9980f5c02a3306461b1a308ff6e3
5
5
  SHA512:
6
- metadata.gz: 8e9bb63e8ac85773680d3f83daff09b18a8c440e5272bac926e10d1dd30142969b5442168965711e40eddd9882c05ac592323f0a46410fa6390a3f70064e8521
7
- data.tar.gz: 16bc718c65e6fa50493e9cdc1cc87ae51264f1873cb68baac0db52915a22eb8c383c52523941ab7d2f7d14f86fad2cb6996b74e4e5c175fa7a4e5da3a75272cb
6
+ metadata.gz: 3d5307f599d3822291a99d931cacb1dc70218e32a120e136cebe98790e89f803e0ad84a5520b6961279c88048a7c2262c91600d770f07639cb5e31be32ff3e5a
7
+ data.tar.gz: 341cfa488dd91fe1b566dfc7c0a4d28dd6ab42e5fb0eb874250ca4b0482e3bb154d398c1c0919fef09a812a1cae7f897542b5702eb097c83e80e534cbbf777bd
@@ -3577,6 +3577,47 @@
3577
3577
  ] });
3578
3578
  }
3579
3579
 
3580
+ // app/assets/typescript/profiler/components/dashboard/tabs/JobsTab.tsx
3581
+ function JobsTab({ jobs }) {
3582
+ if (!jobs?.length) {
3583
+ return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: [
3584
+ /* @__PURE__ */ u3("div", { class: "profiler-empty__icon", children: "\u2699\uFE0F" }),
3585
+ /* @__PURE__ */ u3("h3", { class: "profiler-empty__title", children: "No jobs triggered" }),
3586
+ /* @__PURE__ */ u3("p", { class: "profiler-empty__description", children: "Background jobs enqueued during this request will appear here." })
3587
+ ] });
3588
+ }
3589
+ return /* @__PURE__ */ u3(k, { children: [
3590
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: [
3591
+ "Background Jobs (",
3592
+ jobs.length,
3593
+ ")"
3594
+ ] }),
3595
+ jobs.map((job, index) => /* @__PURE__ */ u3("div", { class: `profiler-ajax-card profiler-ajax-card--${job.status === "completed" ? "success" : job.status === "failed" ? "error" : "default"}`, children: [
3596
+ /* @__PURE__ */ u3("div", { class: "profiler-ajax-card__row", children: [
3597
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-3", children: [
3598
+ /* @__PURE__ */ u3("span", { class: `badge-${job.status === "completed" ? "success" : job.status === "failed" ? "error" : "warning"}`, children: job.status ?? "unknown" }),
3599
+ /* @__PURE__ */ u3("strong", { class: "profiler-ajax-card__path", children: job.job_class })
3600
+ ] }),
3601
+ /* @__PURE__ */ u3("span", { class: job.duration >= 1e3 ? "badge-error" : job.duration >= 200 ? "badge-warning" : "badge-success", children: [
3602
+ job.duration?.toFixed(2),
3603
+ " ms"
3604
+ ] })
3605
+ ] }),
3606
+ /* @__PURE__ */ u3("div", { class: "profiler-ajax-card__row", children: [
3607
+ /* @__PURE__ */ u3("span", { class: "profiler-ajax-card__time profiler-text--muted", children: [
3608
+ job.queue && /* @__PURE__ */ u3("span", { children: [
3609
+ "Queue: ",
3610
+ /* @__PURE__ */ u3("strong", { children: job.queue }),
3611
+ " \xB7 "
3612
+ ] }),
3613
+ new Date(job.started_at).toLocaleTimeString("en", { hour12: false })
3614
+ ] }),
3615
+ /* @__PURE__ */ u3("a", { href: `/_profiler/profiles/${job.token}`, class: "profiler-text--sm", style: "color: var(--profiler-accent);", children: "View Job \u2192" })
3616
+ ] })
3617
+ ] }, index))
3618
+ ] });
3619
+ }
3620
+
3580
3621
  // app/assets/typescript/profiler/components/dashboard/ProfileDashboard.tsx
3581
3622
  function ProfileDashboard({ profile, initialTab, embedded }) {
3582
3623
  const cd = profile.collectors_data || {};
@@ -3586,6 +3627,7 @@
3586
3627
  const hasLogs = (cd["logs"]?.count ?? 0) > 0;
3587
3628
  const hasRoutes = (cd["routes"]?.total ?? 0) > 0;
3588
3629
  const hasI18n = (cd["i18n"]?.total ?? 0) > 0;
3630
+ const hasJobs = (profile.child_jobs?.length ?? 0) > 0;
3589
3631
  const [activeTab, setActiveTab] = d2(hasException ? "exception" : initialTab);
3590
3632
  const handleTabClick = (tab) => (e3) => {
3591
3633
  e3.preventDefault();
@@ -3641,7 +3683,12 @@
3641
3683
  /* @__PURE__ */ u3("a", { href: "#", class: tabClass("cache"), onClick: handleTabClick("cache"), children: "Cache" }),
3642
3684
  hasLogs && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("logs"), onClick: handleTabClick("logs"), children: "Logs" }),
3643
3685
  hasRoutes && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("routes"), onClick: handleTabClick("routes"), children: "Routes" }),
3644
- hasI18n && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("i18n"), onClick: handleTabClick("i18n"), children: "I18n" })
3686
+ hasI18n && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("i18n"), onClick: handleTabClick("i18n"), children: "I18n" }),
3687
+ hasJobs && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("jobs"), onClick: handleTabClick("jobs"), children: [
3688
+ "Jobs (",
3689
+ profile.child_jobs.length,
3690
+ ")"
3691
+ ] })
3645
3692
  ] }),
3646
3693
  /* @__PURE__ */ u3("div", { class: "profiler-p-4 tab-content active", children: [
3647
3694
  activeTab === "exception" && /* @__PURE__ */ u3(ExceptionTab, { exceptionData: cd["exception"] }),
@@ -3655,7 +3702,8 @@
3655
3702
  activeTab === "cache" && /* @__PURE__ */ u3(CacheTab, { cacheData: cd["cache"] }),
3656
3703
  activeTab === "logs" && /* @__PURE__ */ u3(LogsTab, { logData: cd["logs"] }),
3657
3704
  activeTab === "routes" && /* @__PURE__ */ u3(RoutesTab, { routesData: cd["routes"] }),
3658
- activeTab === "i18n" && /* @__PURE__ */ u3(I18nTab, { i18nData: cd["i18n"] })
3705
+ activeTab === "i18n" && /* @__PURE__ */ u3(I18nTab, { i18nData: cd["i18n"] }),
3706
+ activeTab === "jobs" && /* @__PURE__ */ u3(JobsTab, { jobs: profile.child_jobs })
3659
3707
  ] })
3660
3708
  ] }),
3661
3709
  !embedded && /* @__PURE__ */ u3("div", { class: "profiler-mt-6", children: /* @__PURE__ */ u3("a", { href: "/_profiler", style: "color: var(--profiler-accent);", children: "\u2190 Back to profiles" }) })
@@ -3718,13 +3766,15 @@
3718
3766
 
3719
3767
  // app/assets/typescript/profiler/components/dashboard/JobProfileDashboard.tsx
3720
3768
  function JobProfileDashboard({ profile, initialTab, embedded }) {
3721
- const validTabs = ["job", "database", "cache", "http"];
3769
+ const validTabs = ["job", "database", "cache", "http", "jobs"];
3722
3770
  const defaultTab = validTabs.includes(initialTab) ? initialTab : "job";
3723
3771
  const [activeTab, setActiveTab] = d2(defaultTab);
3724
3772
  const cd = profile.collectors_data || {};
3725
3773
  const hasHttp = cd["http"]?.total_requests > 0;
3774
+ const hasJobs = (profile.child_jobs?.length ?? 0) > 0;
3726
3775
  const jobData = cd["job"];
3727
3776
  const isFailed = jobData?.status === "failed";
3777
+ const parent = profile.parent_profile;
3728
3778
  const handleTabClick = (tab) => (e3) => {
3729
3779
  e3.preventDefault();
3730
3780
  setActiveTab(tab);
@@ -3759,6 +3809,26 @@
3759
3809
  " MB"
3760
3810
  ] })
3761
3811
  ] })
3812
+ ] }),
3813
+ parent && /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2 profiler-mt-2 profiler-text--sm", children: [
3814
+ /* @__PURE__ */ u3("span", { style: "color:var(--profiler-text-muted)", children: "Triggered by:" }),
3815
+ parent.profile_type === "http" ? /* @__PURE__ */ u3("span", { children: [
3816
+ /* @__PURE__ */ u3("strong", { children: parent.method }),
3817
+ " ",
3818
+ parent.path,
3819
+ " \xB7 ",
3820
+ parent.http_status,
3821
+ " \xB7 ",
3822
+ parent.duration?.toFixed(2),
3823
+ " ms"
3824
+ ] }) : /* @__PURE__ */ u3("span", { children: [
3825
+ "\u2699\uFE0F ",
3826
+ /* @__PURE__ */ u3("strong", { children: parent.path }),
3827
+ " \xB7 ",
3828
+ parent.duration?.toFixed(2),
3829
+ " ms"
3830
+ ] }),
3831
+ /* @__PURE__ */ u3("a", { href: `/_profiler/profiles/${parent.token}`, style: "color: var(--profiler-accent);", children: "View \u2192" })
3762
3832
  ] })
3763
3833
  ] }),
3764
3834
  /* @__PURE__ */ u3("div", { class: "profiler-panel profiler-mb-6", children: [
@@ -3766,13 +3836,19 @@
3766
3836
  /* @__PURE__ */ u3("a", { href: "#", class: tabClass("job"), onClick: handleTabClick("job"), children: "Job" }),
3767
3837
  /* @__PURE__ */ u3("a", { href: "#", class: tabClass("database"), onClick: handleTabClick("database"), children: "Database" }),
3768
3838
  /* @__PURE__ */ u3("a", { href: "#", class: tabClass("cache"), onClick: handleTabClick("cache"), children: "Cache" }),
3769
- hasHttp && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("http"), onClick: handleTabClick("http"), children: "Outbound HTTP" })
3839
+ hasHttp && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("http"), onClick: handleTabClick("http"), children: "Outbound HTTP" }),
3840
+ hasJobs && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("jobs"), onClick: handleTabClick("jobs"), children: [
3841
+ "Jobs (",
3842
+ profile.child_jobs.length,
3843
+ ")"
3844
+ ] })
3770
3845
  ] }),
3771
3846
  /* @__PURE__ */ u3("div", { class: "profiler-p-4 tab-content active", children: [
3772
3847
  activeTab === "job" && /* @__PURE__ */ u3(JobTab, { jobData: cd["job"] }),
3773
3848
  activeTab === "database" && /* @__PURE__ */ u3(DatabaseTab, { dbData: cd["database"], token: profile.token }),
3774
3849
  activeTab === "cache" && /* @__PURE__ */ u3(CacheTab, { cacheData: cd["cache"] }),
3775
- activeTab === "http" && /* @__PURE__ */ u3(HttpTab, { httpData: cd["http"] })
3850
+ activeTab === "http" && /* @__PURE__ */ u3(HttpTab, { httpData: cd["http"] }),
3851
+ activeTab === "jobs" && /* @__PURE__ */ u3(JobsTab, { jobs: profile.child_jobs })
3776
3852
  ] })
3777
3853
  ] }),
3778
3854
  !embedded && /* @__PURE__ */ u3("div", { class: "profiler-mt-6", children: /* @__PURE__ */ u3("a", { href: "/_profiler", style: "color: var(--profiler-accent);", children: "\u2190 Back to profiles" }) })
@@ -26,7 +26,10 @@ module Profiler
26
26
  return render json: { error: "Job profile not found" }, status: :not_found
27
27
  end
28
28
 
29
- render json: profile.to_h
29
+ render json: profile.to_h.merge(
30
+ child_jobs: build_child_jobs(profile),
31
+ parent_profile: build_parent_summary(profile)
32
+ )
30
33
  end
31
34
 
32
35
  def destroy
@@ -29,7 +29,10 @@ module Profiler
29
29
  # Recalculate AJAX collector data (since AJAX requests happen after page load)
30
30
  recalculate_ajax_data(profile)
31
31
 
32
- render json: profile.to_h
32
+ render json: profile.to_h.merge(
33
+ child_jobs: build_child_jobs(profile),
34
+ parent_profile: build_parent_summary(profile)
35
+ )
33
36
  end
34
37
 
35
38
  def destroy
@@ -15,5 +15,51 @@ module Profiler
15
15
  render plain: "Profiler is disabled", status: :forbidden
16
16
  end
17
17
  end
18
+
19
+ def build_child_jobs(profile)
20
+ Profiler.storage.find_by_parent(profile.token)
21
+ .select { |p| p.profile_type == "job" }
22
+ .map do |j|
23
+ job_data = j.collector_data("job") || {}
24
+ {
25
+ token: j.token,
26
+ job_class: j.path,
27
+ job_id: job_data["job_id"],
28
+ queue: job_data["queue"],
29
+ status: job_data["status"],
30
+ duration: j.duration,
31
+ started_at: j.started_at&.iso8601
32
+ }
33
+ end
34
+ end
35
+
36
+ def build_parent_summary(profile)
37
+ return nil unless profile.parent_token
38
+
39
+ parent = Profiler.storage.load(profile.parent_token)
40
+ return nil unless parent
41
+
42
+ if parent.profile_type == "job"
43
+ job_data = parent.collector_data("job") || {}
44
+ {
45
+ token: parent.token,
46
+ profile_type: "job",
47
+ path: parent.path,
48
+ status: job_data["status"],
49
+ duration: parent.duration,
50
+ started_at: parent.started_at&.iso8601
51
+ }
52
+ else
53
+ {
54
+ token: parent.token,
55
+ profile_type: "http",
56
+ method: parent.method,
57
+ path: parent.path,
58
+ http_status: parent.status,
59
+ duration: parent.duration,
60
+ started_at: parent.started_at&.iso8601
61
+ }
62
+ end
63
+ end
18
64
  end
19
65
  end
@@ -23,6 +23,11 @@ module Profiler
23
23
  # Recalculate AJAX collector data (since AJAX requests happen after page load)
24
24
  recalculate_ajax_data(@profile)
25
25
 
26
+ @profile_data = @profile.to_h.merge(
27
+ child_jobs: build_child_jobs(@profile),
28
+ parent_profile: build_parent_summary(@profile)
29
+ )
30
+
26
31
  @embedded = params[:embed] == "true"
27
32
 
28
33
  render layout: @embedded ? "profiler/embedded" : "profiler/application"
@@ -1,4 +1,4 @@
1
1
  <div id="profiler-show" data-embedded="<%= @embedded %>"></div>
2
2
  <script type="application/json" id="profiler-show-data">
3
- <%= @profile.to_json.html_safe %>
3
+ <%= @profile_data.to_json.html_safe %>
4
4
  </script>
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Profiler
4
+ module CurrentContext
5
+ def self.token
6
+ Thread.current[:profiler_token]
7
+ end
8
+
9
+ def self.token=(value)
10
+ Thread.current[:profiler_token] = value
11
+ end
12
+
13
+ def self.clear
14
+ Thread.current[:profiler_token] = nil
15
+ end
16
+ end
17
+ end
@@ -6,6 +6,12 @@ module Profiler
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
+ attr_accessor :profiler_parent_token
10
+
11
+ before_enqueue do |job|
12
+ job.profiler_parent_token = Profiler::CurrentContext.token
13
+ end
14
+
9
15
  around_perform do |job, block|
10
16
  Profiler::JobProfiler.profile(
11
17
  job_class: job.class.name,
@@ -13,10 +19,20 @@ module Profiler
13
19
  queue: job.queue_name,
14
20
  arguments: job.arguments,
15
21
  executions: job.executions - 1,
22
+ parent_token: job.profiler_parent_token,
16
23
  &block
17
24
  )
18
25
  end
19
26
  end
27
+
28
+ def serialize
29
+ super.merge("profiler_parent_token" => profiler_parent_token)
30
+ end
31
+
32
+ def deserialize(job_data)
33
+ super
34
+ self.profiler_parent_token = job_data["profiler_parent_token"]
35
+ end
20
36
  end
21
37
  end
22
38
  end
@@ -2,6 +2,13 @@
2
2
 
3
3
  module Profiler
4
4
  module Instrumentation
5
+ class SidekiqClientMiddleware
6
+ def call(_worker_class, job, _queue, _redis_pool)
7
+ job["profiler_parent_token"] = Profiler::CurrentContext.token
8
+ yield
9
+ end
10
+ end
11
+
5
12
  class SidekiqMiddleware
6
13
  def call(worker, job, queue, &block)
7
14
  Profiler::JobProfiler.profile(
@@ -10,6 +17,7 @@ module Profiler
10
17
  queue: queue,
11
18
  arguments: job["args"],
12
19
  executions: job["retry_count"].to_i,
20
+ parent_token: job["profiler_parent_token"],
13
21
  &block
14
22
  )
15
23
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "models/profile"
4
+ require_relative "current_context"
4
5
  require_relative "collectors/job_collector"
5
6
  require_relative "collectors/database_collector"
6
7
  require_relative "collectors/cache_collector"
@@ -14,7 +15,7 @@ module Profiler
14
15
  Collectors::HttpCollector
15
16
  ].freeze
16
17
 
17
- def self.profile(job_class:, job_id:, queue:, arguments:, executions:, &block)
18
+ def self.profile(job_class:, job_id:, queue:, arguments:, executions:, parent_token: nil, &block)
18
19
  return block.call unless Profiler.enabled? && Profiler.configuration.track_jobs
19
20
 
20
21
  new(
@@ -22,16 +23,18 @@ module Profiler
22
23
  job_id: job_id,
23
24
  queue: queue,
24
25
  arguments: arguments,
25
- executions: executions
26
+ executions: executions,
27
+ parent_token: parent_token
26
28
  ).run(&block)
27
29
  end
28
30
 
29
- def initialize(job_class:, job_id:, queue:, arguments:, executions:)
31
+ def initialize(job_class:, job_id:, queue:, arguments:, executions:, parent_token: nil)
30
32
  @job_class = job_class
31
33
  @job_id = job_id
32
34
  @queue = queue
33
35
  @arguments = arguments
34
36
  @executions = executions
37
+ @parent_token = parent_token
35
38
  end
36
39
 
37
40
  def run(&block)
@@ -39,6 +42,7 @@ module Profiler
39
42
  profile.profile_type = "job"
40
43
  profile.path = @job_class
41
44
  profile.method = "JOB"
45
+ profile.parent_token = @parent_token if @parent_token
42
46
 
43
47
  job_collector = Collectors::JobCollector.new(profile, {
44
48
  job_class: @job_class,
@@ -56,6 +60,8 @@ module Profiler
56
60
  job_status = "completed"
57
61
  error_message = nil
58
62
 
63
+ previous_token = Profiler::CurrentContext.token
64
+ Profiler::CurrentContext.token = profile.token
59
65
  begin
60
66
  result = block.call
61
67
  result
@@ -64,6 +70,7 @@ module Profiler
64
70
  error_message = "#{e.class}: #{e.message}"
65
71
  raise
66
72
  ensure
73
+ Profiler::CurrentContext.token = previous_token
67
74
  if Profiler.configuration.track_memory
68
75
  profile.memory = current_memory - memory_before
69
76
  end
@@ -63,17 +63,21 @@ module Profiler
63
63
  lines += section_http(profile) if want.("http")
64
64
  lines += section_routes(profile) if want.("routes")
65
65
  lines += section_dumps(profile) if want.("dumps")
66
+ lines += section_related_jobs(profile) if want.("related_jobs")
66
67
  lines.join("\n")
67
68
  end
68
69
 
69
70
  def self.section_overview(profile)
70
71
  lines = []
71
72
  lines << "# Profile Details: #{profile.token}\n"
73
+ lines << "**Type:** #{profile.profile_type == 'job' ? 'Job' : 'HTTP Request'}"
72
74
  lines << "**Request:** #{profile.method} #{profile.path}"
73
75
  lines << "**Status:** #{profile.status}"
74
76
  lines << "**Duration:** #{profile.duration.round(2)} ms"
75
77
  lines << "**Memory:** #{(profile.memory / 1024.0 / 1024.0).round(2)} MB" if profile.memory
76
- lines << "**Time:** #{profile.started_at}\n"
78
+ lines << "**Time:** #{profile.started_at}"
79
+ lines << "**Parent Token:** #{profile.parent_token}" if profile.parent_token
80
+ lines << ""
77
81
  lines
78
82
  end
79
83
 
@@ -386,6 +390,48 @@ module Profiler
386
390
  lines
387
391
  end
388
392
 
393
+ def self.section_related_jobs(profile)
394
+ lines = []
395
+
396
+ # Parent info
397
+ if profile.parent_token
398
+ parent = Profiler.storage.load(profile.parent_token)
399
+ if parent
400
+ lines << "## Triggered By"
401
+ if parent.profile_type == "job"
402
+ job_data = parent.collector_data("job") || {}
403
+ lines << "- **Type:** Job"
404
+ lines << "- **Class:** #{job_data['job_class'] || parent.path}"
405
+ lines << "- **Status:** #{job_data['status']}"
406
+ lines << "- **Duration:** #{parent.duration.round(2)} ms"
407
+ lines << "- **Token:** #{parent.token}"
408
+ else
409
+ lines << "- **Type:** HTTP Request"
410
+ lines << "- **Request:** #{parent.method} #{parent.path}"
411
+ lines << "- **Status:** #{parent.status}"
412
+ lines << "- **Duration:** #{parent.duration.round(2)} ms"
413
+ lines << "- **Token:** #{parent.token}"
414
+ end
415
+ lines << ""
416
+ end
417
+ end
418
+
419
+ # Child jobs
420
+ child_jobs = Profiler.storage.find_by_parent(profile.token).select { |p| p.profile_type == "job" }
421
+ return lines if child_jobs.empty?
422
+
423
+ lines << "## Child Jobs (#{child_jobs.size})"
424
+ lines << ""
425
+ lines << "| Job Class | Status | Duration | Token |"
426
+ lines << "|-----------|--------|----------|-------|"
427
+ child_jobs.each do |job|
428
+ job_data = job.collector_data("job") || {}
429
+ lines << "| #{job_data['job_class'] || job.path} | #{job_data['status'] || '-'} | #{job.duration.round(2)} ms | #{job.token} |"
430
+ end
431
+ lines << ""
432
+ lines
433
+ end
434
+
389
435
  def self.generate_curl(profile, req_data)
390
436
  headers = req_data&.dig("headers") || {}
391
437
  params = req_data&.dig("params") || {}
@@ -4,7 +4,7 @@ module Profiler
4
4
  module MCP
5
5
  module Tools
6
6
  class QueryJobs
7
- ALL_FIELDS = %w[time job_class queue status duration token].freeze
7
+ ALL_FIELDS = %w[time job_class queue status duration token parent_token].freeze
8
8
 
9
9
  def self.call(params)
10
10
  limit = params["limit"]&.to_i || 20
@@ -59,12 +59,13 @@ module Profiler
59
59
  job_data = profile.collector_data("job") || {}
60
60
  row = fields.map do |f|
61
61
  case f
62
- when "time" then profile.started_at.strftime("%H:%M:%S")
63
- when "job_class" then job_data["job_class"] || profile.path
64
- when "queue" then job_data["queue"] || "-"
65
- when "status" then job_data["status"] || "-"
66
- when "duration" then "#{profile.duration.round(2)}ms"
67
- when "token" then profile.token.to_s
62
+ when "time" then profile.started_at.strftime("%H:%M:%S")
63
+ when "job_class" then job_data["job_class"] || profile.path
64
+ when "queue" then job_data["queue"] || "-"
65
+ when "status" then job_data["status"] || "-"
66
+ when "duration" then "#{profile.duration.round(2)}ms"
67
+ when "token" then profile.token.to_s
68
+ when "parent_token" then profile.parent_token || "-"
68
69
  end
69
70
  end
70
71
  lines << "| #{row.join(' | ')} |"
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../models/profile"
4
+ require_relative "../current_context"
4
5
  require_relative "toolbar_injector"
5
6
 
6
7
  module Profiler
@@ -14,6 +15,7 @@ module Profiler
14
15
  return @app.call(env) unless should_profile?(env)
15
16
 
16
17
  profile = Models::Profile.new(build_request(env))
18
+ Profiler::CurrentContext.token = profile.token
17
19
 
18
20
  # Capture request body before app processes it
19
21
  req_body_raw = read_rack_input(env)
@@ -64,6 +66,7 @@ module Profiler
64
66
 
65
67
  # Store profile
66
68
  Profiler.storage.save(profile.token, profile)
69
+ Profiler::CurrentContext.clear
67
70
 
68
71
  # Add profiler token header
69
72
  headers["X-Profiler-Token"] = profile.token
@@ -62,6 +62,11 @@ module Profiler
62
62
  chain.add Profiler::Instrumentation::SidekiqMiddleware
63
63
  end
64
64
  end
65
+ Sidekiq.configure_client do |config|
66
+ config.client_middleware do |chain|
67
+ chain.add Profiler::Instrumentation::SidekiqClientMiddleware
68
+ end
69
+ end
65
70
  end
66
71
 
67
72
  if defined?(ActiveJob::Base)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Profiler
4
- VERSION = "0.13.0"
4
+ VERSION = "0.14.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sébastien Duplessy
@@ -139,6 +139,7 @@ files:
139
139
  - lib/profiler/collectors/routes_collector.rb
140
140
  - lib/profiler/collectors/view_collector.rb
141
141
  - lib/profiler/configuration.rb
142
+ - lib/profiler/current_context.rb
142
143
  - lib/profiler/engine.rb
143
144
  - lib/profiler/explain_runner.rb
144
145
  - lib/profiler/instrumentation/active_job_instrumentation.rb