sentiero 1.0.0.beta1 → 1.0.0.beta3

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: c8bc35b365449e9111ed5dcc093ea7fceaf60d739b47e1bf13df8f7cbc83cf55
4
- data.tar.gz: 39c9a76c8acf2f22d44fcc3fa88d450ef1e36a368434ba5ae143c82bd2800b30
3
+ metadata.gz: ed5c945cbabf6b9ae1ab2b3e10474d06bc06bda78c1a4b381b69845791c18296
4
+ data.tar.gz: abb85bfebbdb1e8c48cba2eb844b6446d5bd82f17e7727c3b00347b5256de38e
5
5
  SHA512:
6
- metadata.gz: 527083a0dc823bb03343f97b41f3f40e3c629ce201dbf234613c9d615068e8e481d612f0aa0ff8a5736e6cc487f1f6d049f0d704647b0fa37e7361d4cafa4003
7
- data.tar.gz: c77ebaff3d74da9cdef241db684ccb6f0b5a141a10932fd49d3af131bd4434ddb8194ee78abab087a16893c2b01a4566f0f75f98f44e17679e5107bfb2a9b0dc
6
+ metadata.gz: c5ef1855f03fc35d2b719ee316979de2ae3331b2400bfb534a0406048fe31cd9b292e252fdda5a59f54f73a066c33aed17aecaf88c2e697a82d0269ddd525f84
7
+ data.tar.gz: c7fce88e343ca579a5add1656f6e12b997162a5c7dbe62630e21b00334e13e3bc88f51dcabc9878f8ad93712851b626f5475cd76bec870eb1f142a7089959021
@@ -48,6 +48,7 @@ module Sentiero
48
48
 
49
49
  key = ErrorCollector.group_key(message)
50
50
  group = groups[key] ||= new_group(key, message, payload)
51
+ group[:stack] ||= stack_of(payload)
51
52
  group[:count] += 1
52
53
  group[:last_seen_at] = [group[:last_seen_at], timestamp].compact.max
53
54
  tally_facets(group, summary[:metadata] || {})
@@ -72,6 +73,7 @@ module Sentiero
72
73
  message: message,
73
74
  source: source_of(payload),
74
75
  line: line_of(payload),
76
+ stack: nil,
75
77
  count: 0,
76
78
  last_seen_at: nil,
77
79
  browsers: Hash.new(0),
@@ -105,6 +107,11 @@ module Sentiero
105
107
  line.is_a?(Integer) ? line : nil
106
108
  end
107
109
 
110
+ def stack_of(payload)
111
+ stack = payload["stack"]
112
+ (stack.is_a?(String) && !stack.empty?) ? stack : nil
113
+ end
114
+
108
115
  def sort_groups(groups, sort_by)
109
116
  case sort_by
110
117
  when "recency"
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+
5
+ module Sentiero
6
+ module Stores
7
+ class SQLite
8
+ # zlib compression for events.data payloads. FullSnapshots dominate the
9
+ # table's bytes (~93% in production samples) and deflate ~6-7x, so
10
+ # payloads at or above the threshold are stored as compressed BLOBs;
11
+ # smaller rows stay plain JSON TEXT (near-zero win, and skipping them
12
+ # keeps ~85% of rows out of the deflate path). Reads sniff the storage
13
+ # format from the payload itself: a zlib stream starts with 0x78 and a
14
+ # valid header checksum, which no JSON text can.
15
+ module PayloadCodec
16
+ COMPRESSION_THRESHOLD = 1024
17
+ # CMF byte for deflate with a 32K window — the only one zlib emits.
18
+ ZLIB_FIRST_BYTE = 0x78
19
+
20
+ module_function
21
+
22
+ def encode(json)
23
+ return json if json.bytesize < COMPRESSION_THRESHOLD
24
+
25
+ ::SQLite3::Blob.new(Zlib::Deflate.deflate(json))
26
+ end
27
+
28
+ def decode(data)
29
+ return data unless compressed?(data)
30
+
31
+ Zlib::Inflate.inflate(data).force_encoding(Encoding::UTF_8)
32
+ end
33
+
34
+ def compressed?(data)
35
+ data.getbyte(0) == ZLIB_FIRST_BYTE &&
36
+ (flg = data.getbyte(1)) &&
37
+ (((ZLIB_FIRST_BYTE << 8) | flg) % 31).zero?
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -83,6 +83,7 @@ module Sentiero
83
83
  SQL
84
84
 
85
85
  migrate_events_type!(db)
86
+ migrate_compress_payloads!(db)
86
87
  end
87
88
 
88
89
  # Databases created before the events.type column existed get a
@@ -110,6 +111,34 @@ module Sentiero
110
111
  SQL
111
112
  end
112
113
  end
114
+
115
+ # user_version 1 = every large payload is zlib-compressed (PayloadCodec).
116
+ SCHEMA_VERSION = 1
117
+
118
+ # One-time pass compressing pre-existing plain-TEXT payloads. Batched
119
+ # in short transactions so a live recorder's ingest writes interleave
120
+ # instead of timing out behind one giant lock. Guarded by
121
+ # PRAGMA user_version, so subsequent opens skip the table scan.
122
+ # Frees pages for reuse but does not shrink the file — VACUUM manually
123
+ # to reclaim disk.
124
+ def self.migrate_compress_payloads!(db)
125
+ return if db.get_first_value("PRAGMA user_version").to_i >= SCHEMA_VERSION
126
+
127
+ loop do
128
+ rows = db.execute(
129
+ "SELECT id, data FROM events WHERE typeof(data) = 'text' AND LENGTH(data) >= ? LIMIT 500",
130
+ [PayloadCodec::COMPRESSION_THRESHOLD]
131
+ )
132
+ break if rows.empty?
133
+
134
+ db.transaction(:immediate) do
135
+ rows.each do |row|
136
+ db.execute("UPDATE events SET data = ? WHERE id = ?", [PayloadCodec.encode(row["data"]), row["id"]])
137
+ end
138
+ end
139
+ end
140
+ db.execute("PRAGMA user_version = #{SCHEMA_VERSION}")
141
+ end
113
142
  end
114
143
  end
115
144
  end
@@ -16,6 +16,7 @@ module Sentiero
16
16
  class SQLite < Store
17
17
  # Loaded after the class line above establishes SQLite < Store, so
18
18
  # schema.rb's own `class SQLite` reopen doesn't hit a superclass mismatch.
19
+ require_relative "sqlite/payload_codec"
19
20
  require_relative "sqlite/schema"
20
21
 
21
22
  # Single-file SQLite store for single-process production and dev.
@@ -101,7 +102,7 @@ module Sentiero
101
102
  events.each do |event|
102
103
  type = event["type"]
103
104
  stmt.execute(session_id, window_id, event["timestamp"]&.to_f,
104
- type.is_a?(Integer) ? type : nil, JSON.generate(event))
105
+ type.is_a?(Integer) ? type : nil, PayloadCodec.encode(JSON.generate(event)))
105
106
  end
106
107
  ensure
107
108
  stmt.close
@@ -300,7 +301,7 @@ module Sentiero
300
301
  params << limit.to_i
301
302
  end
302
303
 
303
- db.execute(sql, params).map { |event_row| JSON.parse(event_row["data"]) }
304
+ db.execute(sql, params).map { |event_row| JSON.parse(PayloadCodec.decode(event_row["data"])) }
304
305
  end
305
306
 
306
307
  def save_metadata(session_id, metadata)
@@ -570,7 +571,7 @@ module Sentiero
570
571
  "SELECT session_id, window_id, data FROM events WHERE session_id IN (#{placeholders})#{type_clause} ORDER BY timestamp ASC",
571
572
  chunk + type_params
572
573
  ).each do |row|
573
- (grouped[row["session_id"]][row["window_id"]] ||= []) << JSON.parse(row["data"])
574
+ (grouped[row["session_id"]][row["window_id"]] ||= []) << JSON.parse(PayloadCodec.decode(row["data"]))
574
575
  end
575
576
  end
576
577
  grouped
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentiero
4
- VERSION = "1.0.0.beta1"
4
+ VERSION = "1.0.0.beta3"
5
5
  end
@@ -23,6 +23,11 @@
23
23
  <div class="text-xs text-gray-600">
24
24
  <%= view.group[:last_seen_at] ? Time.at(view.group[:last_seen_at].to_f / 1000).utc.strftime("%b %d, %Y %H:%M:%S UTC") : "N/A" %>
25
25
  </div>
26
+
27
+ <% if view.group[:stack] -%>
28
+ <div class="text-[10px] font-medium text-gray-400 uppercase tracking-wider pt-0.5">Stack</div>
29
+ <pre class="text-[10px] text-gray-500 bg-gray-50 rounded p-2 overflow-x-auto whitespace-pre-wrap font-mono" data-client-error-stack><%= view.h(view.group[:stack]) %></pre>
30
+ <% end -%>
26
31
  </div>
27
32
  </div>
28
33
  </div>
@@ -36,7 +41,7 @@
36
41
  <% view.facet_chips.each do |title, counts| -%>
37
42
  <% next if counts.empty? -%>
38
43
  <div class="flex items-center gap-2 mb-1.5 flex-wrap">
39
- <span class="text-[10px] font-medium text-gray-400 uppercase tracking-wider w-16 shrink-0"><%= view.h(title) %></span>
44
+ <span class="text-[10px] font-medium text-gray-400 uppercase tracking-wider w-20 shrink-0"><%= view.h(title) %></span>
40
45
  <% counts.sort_by { |_value, count| -count }.first(8).each do |value, count| -%>
41
46
  <span class="badge badge-neutral"><%= view.h(value) %>&nbsp;<span class="tabular-nums">&times;<%= count %></span></span>
42
47
  <% end -%>
@@ -20,7 +20,7 @@ module Sentiero
20
20
  [
21
21
  ["Browsers", group[:browsers] || {}],
22
22
  ["Devices", group[:devices] || {}],
23
- ["Pages", group[:pages] || {}]
23
+ ["Entry pages", group[:pages] || {}]
24
24
  ]
25
25
  end
26
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentiero
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta1
4
+ version: 1.0.0.beta3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Ierodiaconou
@@ -117,6 +117,7 @@ files:
117
117
  - lib/sentiero/stores/redis/keys.rb
118
118
  - lib/sentiero/stores/redis/lua.rb
119
119
  - lib/sentiero/stores/sqlite.rb
120
+ - lib/sentiero/stores/sqlite/payload_codec.rb
120
121
  - lib/sentiero/stores/sqlite/schema.rb
121
122
  - lib/sentiero/user_agent.rb
122
123
  - lib/sentiero/version.rb