rails_observatory 0.1.0 → 0.1.1

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: f127883108bb93b52d5230b2201ca52356acebf2de4ea62dc9cccf1d1c44d1b6
4
- data.tar.gz: 90007d376e64259aad9633c375d7dce81a93d4bec9de7a9a61f9e72d1f39d5ab
3
+ metadata.gz: 493d740aa5e9922dd611aa3425c2830c4cfaa920a3474d7d58383613cabdaf05
4
+ data.tar.gz: 982d077e561ce0895f6c722d690d9aad3ceaf79302d302dfb7afb544bc07a3bc
5
5
  SHA512:
6
- metadata.gz: 6a25967194df5b78f63bbb8e380a146706bf345224e3da519da64556639d9ec9e37cad25a9332d224ea47f4336ae260e74272e69d6ee8498787f3cafdd498432
7
- data.tar.gz: c847561c550175312c4442930dc65a2523dec1c915c97387e3858bf1396c38d262e498f849fc6ca108cb0f8316a69df09138b9844be6f3e55902db18b9134775
6
+ metadata.gz: af0cdab142f36284b8265a1953a4a1663ebf86f267be6854a50a6e61ab213bc5067f38159c61095f1e4ddae5b191e995fac6e902f9febc7c494a81b854555f67
7
+ data.tar.gz: 3e38d9ba1e846b8648eeb714dbe89c39fcca3a216ec050dcda1cebf09a3d19b059b1949013ce8ecc4a5afec018aee1114648f00f42642f3491b4f38ec1f082d1
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Rails Observatory
2
2
 
3
- Simple observability for your Rails app.
3
+ Observability for your Rails app.
4
4
 
5
5
  Rails observatory hooks into ActiveSupport::Instrumentation
6
6
  with [RedisStack](https://redis.io/docs/about/about-stack/) to provide
@@ -38,5 +38,4 @@ Pull requests, issues and more are welcome!
38
38
 
39
39
  ## License
40
40
 
41
- The gem is available as open source under the terms of the LGPL v2 License.
42
- If you'd like a commercial license, please contact me mark.godwin@hey.com
41
+ The gem is available as open source under the terms of the MIT License.
@@ -1,7 +1,7 @@
1
1
  import {Controller} from "@hotwired/stimulus";
2
2
  import Apexcharts from "apexcharts";
3
3
 
4
- const icicleChartOptions = function(controller) {
4
+ const icicleChartOptions = function (controller) {
5
5
  return {
6
6
  chart: {
7
7
  id: controller.element.id,
@@ -9,10 +9,12 @@ const icicleChartOptions = function(controller) {
9
9
  type: 'rangeBar',
10
10
  height: 300,
11
11
  events: {
12
- dataPointSelection: function(event, chartContext, config) {
13
- controller.dispatch('selected', {detail: {
12
+ dataPointSelection: function (event, chartContext, config) {
13
+ controller.dispatch('selected', {
14
+ detail: {
14
15
  ...config.w.config.series[config.seriesIndex].data[config.dataPointIndex]
15
- }})
16
+ }
17
+ })
16
18
  }
17
19
  },
18
20
  },
@@ -69,8 +71,12 @@ const icicleChartOptions = function(controller) {
69
71
  const seriesSelfTime = controller.series()[opts.seriesIndex].data.reduce((acc, val) => {
70
72
  return acc + val['event_self_time']
71
73
  }, 0)
72
- console.log(controller.series()[opts.seriesIndex].name, controller.series()[opts.seriesIndex].data);
73
- const totalSelfTime = controller.series().reduce((acc, val) => { return acc + val.data.reduce((acc, val) => { return acc + val['event_self_time'] }, 0) }, 0)
74
+ // console.log(controller.series()[opts.seriesIndex].name, controller.series()[opts.seriesIndex].data);
75
+ const totalSelfTime = controller.series().reduce((acc, val) => {
76
+ return acc + val.data.reduce((acc, val) => {
77
+ return acc + val['event_self_time']
78
+ }, 0)
79
+ }, 0)
74
80
  const percent = seriesSelfTime / totalSelfTime * 100
75
81
  return `${seriesName} <span class="percent">${seriesSelfTime.toFixed(1)}ms <div class="bar"><div style="width:${percent.toFixed(1)}%"></div></div></span> `
76
82
  },
@@ -97,7 +103,7 @@ const icicleChartOptions = function(controller) {
97
103
 
98
104
  }
99
105
 
100
- const defaultOptions = function(controller) {
106
+ const defaultOptions = function (controller) {
101
107
  return {
102
108
  chart: {
103
109
  id: controller.element.id,
@@ -126,14 +132,6 @@ const defaultOptions = function(controller) {
126
132
  opacityTo: 0
127
133
  }
128
134
  },
129
- theme: {
130
- monochrome: {
131
- enabled: true,
132
- color: '#0c8be8',
133
- shadeTo: 'dark',
134
- shadeIntensity: 0.65
135
- }
136
- },
137
135
  stroke: {
138
136
  width: 2,
139
137
  curve: 'straight'
@@ -142,6 +140,17 @@ const defaultOptions = function(controller) {
142
140
 
143
141
  }
144
142
 
143
+ function stackedAreaOptions(controller) {
144
+ return {
145
+ ...defaultOptions(controller),
146
+ chart: {
147
+ ...defaultOptions(controller).chart,
148
+ type: 'area',
149
+ stacked: true,
150
+ },
151
+ }
152
+ }
153
+
145
154
  export class ChartController extends Controller {
146
155
  static targets = ["chart", "data"]
147
156
 
@@ -157,6 +166,8 @@ export class ChartController extends Controller {
157
166
  switch (this.typeValue) {
158
167
  case 'icicle':
159
168
  return icicleChartOptions(this)
169
+ case 'stackedArea':
170
+ return stackedAreaOptions(this)
160
171
  default:
161
172
  return defaultOptions(this)
162
173
  }
@@ -14,8 +14,13 @@ module RailsObservatory
14
14
  html.to_html
15
15
  end
16
16
 
17
- def series_for(name:, aggregate_using:, time_range: nil, downsample: 120, **opts)
18
- series = RailsObservatory::TimeSeries.where(name:, **opts).downsample(downsample, using: aggregate_using)
17
+ def series_for(name:, aggregate_using:, time_range: nil, downsample: 60, children: false, **opts)
18
+ if children
19
+ series = TimeSeries.where(parent: name, **opts)
20
+ else
21
+ series = TimeSeries.where(name:, **opts)
22
+ end
23
+ series = series.downsample(downsample, using: aggregate_using)
19
24
  series = series.slice(time_range) if time_range
20
25
  series
21
26
  end
@@ -11,9 +11,8 @@
11
11
  <div class="layout-requests_index-chart">
12
12
  <%= render 'chart', name: 'Requests', type: 'bar', series: series_for(name: 'request.count', aggregate_using: :sum, action: params[:controller_action]) %>
13
13
  <%= render 'chart', name: 'Latency', type: 'area', series: series_for(name: 'request.latency', aggregate_using: :avg, action: params[:controller_action]) %>
14
-
15
- <%#= render 'chart', name: "Latency Breakdown", series: @latency_composition, type: 'area' %>
16
- <%#= render 'chart', name: 'Errors', series: @errors, type: 'bar', palette: 'palette7' %>
14
+ <%= render 'chart', name: "Latency Breakdown", series: series_for(name: 'request.latency', aggregate_using: :avg, compaction: :avg, action: params[:controller_action], children: true), type: 'stackedArea' %>
15
+ <%= render 'chart', name: 'Errors', series: series_for(name: 'request.error_count', aggregate_using: :sum, action: params[:controller_action]), type: 'bar' %>
17
16
  </div>
18
17
  <% if params[:controller_action].blank? %>
19
18
  <section class="layout-requests_index-by_controller">
@@ -48,6 +48,7 @@ module RailsObservatory
48
48
  status, headers, body = response
49
49
  body = ::Rack::BodyProxy.new(body) do
50
50
  duration = (Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start_at_mono)
51
+ serialized_events = events.map { Serializer.serialize(_1) }
51
52
  RequestTrace.new(
52
53
  request_id: request.request_id,
53
54
  status:,
@@ -59,13 +60,16 @@ module RailsObservatory
59
60
  duration:,
60
61
  time: start_at.to_f,
61
62
  path: request.path,
62
- events: events.map { Serializer.serialize(_1) },
63
+ events: serialized_events,
63
64
  logs:
64
65
  ).save
65
66
  labels = { action: controller_action, format: request.format, status:, http_method: request.method }
66
67
  TimeSeries.record_occurrence("request.count", labels:)
67
68
  TimeSeries.record_occurrence("request.error_count", labels:) if status >= 500
68
69
  TimeSeries.record_timing("request.latency", duration, labels:)
70
+ EventCollection.new(serialized_events).self_time_by_library.each do |library, self_time|
71
+ TimeSeries.record_timing("request.latency/#{library}", self_time, labels: { action: controller_action })
72
+ end
69
73
  rescue => e
70
74
  puts e
71
75
  puts e.backtrace.join("\n")
@@ -65,6 +65,13 @@ module RailsObservatory
65
65
  end
66
66
  end
67
67
 
68
+ def self_time_by_library
69
+ each_with_object(Hash.new(0)) do |event, hash|
70
+ library = event['name'].split('.').last
71
+ hash[library] += event['self_time']
72
+ end
73
+ end
74
+
68
75
 
69
76
 
70
77
  private
@@ -127,10 +127,11 @@ module RailsObservatory
127
127
  end
128
128
 
129
129
  def ts_filters
130
- raise 'Must specify name' if @conditions[:name].blank?
130
+ root_labels = @conditions[:name] || @conditions[:parent]
131
+ raise 'Must specify name or parent' if @conditions[:name].blank? && @conditions[:parent].blank?
131
132
 
132
133
  @conditions[@group] = "*" if @group
133
- labels = @series_class.redis.call('SMEMBERS', "#{@conditions[:name]}:labels")
134
+ labels = @series_class.redis.call('SMEMBERS', "#{root_labels}:labels")
134
135
  labels = labels.map { |l| [l.to_sym, nil] }.to_h
135
136
  @conditions.reverse_merge!(labels)
136
137
  @conditions.map do |k, v|
@@ -9,7 +9,7 @@ local function generate_key_combinations(keys)
9
9
  for i = start_idx, n do
10
10
  local new_comb = {}
11
11
  for _, v in ipairs(curr_comb) do
12
- table.insert(new_comb, v)
12
+ table.insert(new_comb, v)
13
13
  end
14
14
  table.insert(new_comb, keys[i])
15
15
  table.insert(combs, new_comb)
@@ -36,7 +36,7 @@ local metric_name = tostring(ARGV[1]) -- Ensure it's a string
36
36
  local value_to_add = tonumber(ARGV[2]) -- Ensure it's a number
37
37
  local raw_retention = 10000 -- Hardcoded to 10ms
38
38
  local compaction_retention = 31536000000 -- Hardcoded to 1 year in ms (365*24*60*60*1000)
39
- local compactions = {"avg", "min", "max"}
39
+ local compactions = { "avg", "min", "max" }
40
40
 
41
41
  -- Assuming base_name is defined somewhere above
42
42
  local parent_label = extract_parent_label(metric_name)
@@ -44,11 +44,15 @@ local parent_label = extract_parent_label(metric_name)
44
44
  -- Extracting labels
45
45
  local labels = {}
46
46
  local keys = {}
47
- for i=3, #ARGV, 2 do
47
+ for i = 3, #ARGV, 2 do
48
48
  local key = tostring(ARGV[i])
49
- local value = tostring(ARGV[i+1])
49
+ local value = tostring(ARGV[i + 1])
50
50
  labels[key] = value
51
- redis.call("SADD", metric_name .. ':labels', key)
51
+ if parent_label then
52
+ redis.call("SADD", parent_label .. ':labels', key)
53
+ else
54
+ redis.call("SADD", metric_name .. ':labels', key)
55
+ end
52
56
  table.insert(keys, key)
53
57
  end
54
58
 
@@ -78,9 +82,8 @@ for _, comb_keys in ipairs(key_combinations) do
78
82
  for _, compaction in ipairs(compactions) do
79
83
  local compaction_key = ts_name .. "_" .. compaction
80
84
  if redis.call("EXISTS", compaction_key) == 0 then
81
- redis.call("TS.CREATE", compaction_key, "RETENTION", compaction_retention, "CHUNK_SIZE", 48, "LABELS","name", metric_name, "compaction", compaction, unpack(label_set))
85
+ redis.call("TS.CREATE", compaction_key, "RETENTION", compaction_retention, "CHUNK_SIZE", 48, "LABELS", "name", metric_name, "compaction", compaction, unpack(label_set))
82
86
  redis.call("TS.CREATERULE", ts_name, compaction_key, "AGGREGATION", compaction, 10000)
83
- return redis.call("TS.INFO", compaction_key)
84
87
  end
85
88
  end
86
89
  redis.call("TS.ADD", ts_name, "*", value_to_add)
@@ -34,7 +34,7 @@ module RailsObservatory
34
34
  when Symbol
35
35
  argument.to_s
36
36
  else
37
- ADDITIONAL_SERIALIZERS.find { argument.is_a?(_1.klass) }&.new&.serialize(argument) || "Unable to serialize #{argument.class.name}"
37
+ ADDITIONAL_SERIALIZERS.find { argument.is_a?(_1.klass) }&.new&.serialize(argument)&.deep_stringify_keys || "Unable to serialize #{argument.class.name}"
38
38
  end
39
39
  end
40
40
 
@@ -42,7 +42,7 @@ module RailsObservatory
42
42
  argument.each_with_object({}) do |(key, value), hash|
43
43
  case key
44
44
  when String, Symbol
45
- hash[key] = serialize_payload(value)
45
+ hash[key.to_s] = serialize_payload(value)
46
46
  end
47
47
  end
48
48
  end
@@ -1,3 +1,3 @@
1
1
  module RailsObservatory
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -10850,9 +10850,11 @@
10850
10850
  height: 300,
10851
10851
  events: {
10852
10852
  dataPointSelection: function(event, chartContext, config) {
10853
- controller.dispatch("selected", { detail: {
10854
- ...config.w.config.series[config.seriesIndex].data[config.dataPointIndex]
10855
- } });
10853
+ controller.dispatch("selected", {
10854
+ detail: {
10855
+ ...config.w.config.series[config.seriesIndex].data[config.dataPointIndex]
10856
+ }
10857
+ });
10856
10858
  }
10857
10859
  }
10858
10860
  },
@@ -10903,7 +10905,6 @@
10903
10905
  const seriesSelfTime = controller.series()[opts.seriesIndex].data.reduce((acc, val) => {
10904
10906
  return acc + val["event_self_time"];
10905
10907
  }, 0);
10906
- console.log(controller.series()[opts.seriesIndex].name, controller.series()[opts.seriesIndex].data);
10907
10908
  const totalSelfTime = controller.series().reduce((acc, val) => {
10908
10909
  return acc + val.data.reduce((acc2, val2) => {
10909
10910
  return acc2 + val2["event_self_time"];
@@ -10962,20 +10963,22 @@
10962
10963
  opacityTo: 0
10963
10964
  }
10964
10965
  },
10965
- theme: {
10966
- monochrome: {
10967
- enabled: true,
10968
- color: "#0c8be8",
10969
- shadeTo: "dark",
10970
- shadeIntensity: 0.65
10971
- }
10972
- },
10973
10966
  stroke: {
10974
10967
  width: 2,
10975
10968
  curve: "straight"
10976
10969
  }
10977
10970
  };
10978
10971
  };
10972
+ function stackedAreaOptions(controller) {
10973
+ return {
10974
+ ...defaultOptions(controller),
10975
+ chart: {
10976
+ ...defaultOptions(controller).chart,
10977
+ type: "area",
10978
+ stacked: true
10979
+ }
10980
+ };
10981
+ }
10979
10982
  var ChartController = class extends Controller {
10980
10983
  static targets = ["chart", "data"];
10981
10984
  static values = {
@@ -10989,6 +10992,8 @@
10989
10992
  switch (this.typeValue) {
10990
10993
  case "icicle":
10991
10994
  return icicleChartOptions(this);
10995
+ case "stackedArea":
10996
+ return stackedAreaOptions(this);
10992
10997
  default:
10993
10998
  return defaultOptions(this);
10994
10999
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_observatory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Godwin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-09 00:00:00.000000000 Z
11
+ date: 2024-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faker
@@ -66,7 +66,7 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
- description: Observability for Rails Apps
69
+ description: See what's happening in your Rails App
70
70
  email:
71
71
  - mark.godwin@hey.com
72
72
  executables: []
@@ -193,5 +193,5 @@ requirements: []
193
193
  rubygems_version: 3.4.13
194
194
  signing_key:
195
195
  specification_version: 4
196
- summary: Observability for Rails Apps
196
+ summary: See what's happening in your Rails App
197
197
  test_files: []