rails_observatory 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []