deployangel 0.1.0 → 0.1.2

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: b1740b8bdbebb7cf89db6fe0a93eb1e09f3452492c88ee0d13bac1d6060e6778
4
- data.tar.gz: 95bf36dc26fdf153a4783172dd3296a92e004f0109068ade65716d46ffa18590
3
+ metadata.gz: bd9c6a1966ad36b07d9544c19cfc0b431afd75794ec53f8710792b078e6370ba
4
+ data.tar.gz: 7048de09969cb804f374efbe9e3117402b9e7d048faceb45ad8390707d1e01ac
5
5
  SHA512:
6
- metadata.gz: fc6ba275f576aa06da811002891c7d158008fc729462c3c6cec9a30792a62f891469cffb41e8a9538cf863611d348d94c451d6735c41a9b9a0c63fb2fee692ac
7
- data.tar.gz: 6a85b7eef1c4989d5dcab0790d226e21a9acaee7a5c40592d11c1608eb5f5bef06452dcd01ae41e988d91ce9a6d6a475c0d32bc74051ecf23a18d4346ea002ec
6
+ metadata.gz: 0d11c1b1a206d317f433af474f908fabdc0aae002d36b5977577c5691c9756a0717b2377a4e09815b299ea9c9e096402f74742ca7bb204ae2f2be97bd277b581
7
+ data.tar.gz: 3fa2290369ffdb891d9eb575e4ba90187b3576146a46310e31319352b4ae408b0aae7e1659e5bb45adaf400eea6ca6dab02c032f143d527daf0212a6c5505f17
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2 (2026-10-02)
4
+
5
+ - Unsent minutes wait in the buffer as gzipped JSON rather than Ruby objects.
6
+ With every list at its cap, 10 minutes queued while DeployAngel was
7
+ unreachable took about 20 MB; they now take well under 1 MB.
8
+ - `rake bench` measures the agent's overhead: time and allocations per
9
+ request, memory with every list at its cap, and the file digest pass.
10
+
11
+ ## 0.1.1 (2026-10-02)
12
+
13
+ - Declared Solid Queue schedules carry the time zone Solid Queue reads them
14
+ in (`SolidQueue.time_zone`, which defaults to `config.time_zone`), so
15
+ DeployAngel expects a job scheduled "at 4am every day" at the right hour.
16
+ Without it, a release could fail for a recurring job that wasn't due yet
17
+ when the app's time zone in the dashboard differed from the app's own.
18
+ - `deployangel verify` prints a missing recurring job as "didn't run" and
19
+ when it was expected, instead of its interval as a percentage.
20
+ - The README describes registering deploys on every host before the Heroku
21
+ add-on.
22
+
3
23
  ## 0.1.0 (2026-10-01)
4
24
 
5
25
  - Release identity on Kamal (`KAMAL_VERSION`), Render (`RENDER_GIT_COMMIT`),
data/README.md CHANGED
@@ -78,11 +78,18 @@ user data.
78
78
  in-memory counters.
79
79
  - Payloads are sent from a background thread, once a minute, with short
80
80
  timeouts.
81
- - When DeployAngel is unreachable, the buffer is bounded (10 payloads) and the
82
- oldest are dropped. Your app is never blocked or failed.
81
+ - When DeployAngel is unreachable, the buffer is bounded (10 payloads, kept as
82
+ gzipped JSON) and the oldest are dropped. Your app is never blocked or failed.
83
83
  - Safe across forks (Puma cluster mode and similar), and the minute in progress
84
84
  is flushed at shutdown.
85
85
 
86
+ `bundle exec rake bench` measures this: the time and allocations each request
87
+ adds, memory with every list at its cap, and the one-time file digest pass
88
+ (`APP_ROOT=path/to/app` to digest your own app). On an Apple M-series laptop
89
+ with Ruby 3.4, recording a request adds about 1.6 µs and 6 objects, and with
90
+ every route, job, checkpoint, and exception list at its cap the agent holds
91
+ about 3 MB, including 10 unsent minutes.
92
+
86
93
  ## Configuration
87
94
 
88
95
  Environment variables are enough for most apps. To override in code:
@@ -130,9 +137,10 @@ including IDs, since only 100 distinct names are counted per minute.
130
137
 
131
138
  ## Registering deploys
132
139
 
133
- On Heroku, the add-on registers every release for you. Anywhere else,
134
- DeployAngel notices a new release when the agent first reports it, and verifies
135
- it from there. The releases running when you install the agent are the baseline.
140
+ DeployAngel notices a new release when the agent first reports it, and
141
+ verifies it from there, with nothing to set up. The releases running when you
142
+ install the agent are the baseline. On Heroku, the add-on also registers every
143
+ release for you.
136
144
 
137
145
  Registering deploys yourself adds a link to the CI run and a label of your
138
146
  choice, and starts verification as soon as the deploy finishes. Use an API
@@ -97,13 +97,14 @@ module DeployAngel
97
97
  DeployAngel.capabilities
98
98
  end
99
99
 
100
- # Drains completed minutes into the buffer and sends what it can.
100
+ # Drains completed minutes into the buffer, encoded so unsent minutes stay
101
+ # small while DeployAngel is unreachable, and sends what it can.
101
102
  def flush(include_current: false)
102
103
  return 0 unless @active
103
104
 
104
105
  @aggregator.drain(include_current: include_current, max_periods: config.max_queued_payloads).each do |period|
105
- @buffer.push(Protocol.telemetry(period, instance: @instance, release: @release, runtime: @runtime,
106
- capabilities: capabilities))
106
+ @buffer.push(Transport.encode(Protocol.telemetry(period, instance: @instance, release: @release,
107
+ runtime: @runtime, capabilities: capabilities)))
107
108
  end
108
109
  send_buffered
109
110
  rescue StandardError => e
@@ -70,6 +70,9 @@ module DeployAngel
70
70
  label = "#{LABELS.fetch(finding["signal"], finding["signal"])} on #{finding["scope"]}"
71
71
  if COUNTS.include?(finding["signal"])
72
72
  "#{label}: #{finding["observed_value"].to_i} occurrences"
73
+ elsif finding["signal"] == "missing_recurring_job"
74
+ # Its value is the job's interval in seconds, not a rate.
75
+ "#{label}: #{finding["status"] == "pass" ? "ran" : "didn't run"}, #{finding["threshold"]}"
73
76
  elsif finding["baseline_value"].nil? && finding["observed_value"].nil?
74
77
  "#{label}: #{finding["threshold"]}"
75
78
  else
@@ -16,10 +16,28 @@ module DeployAngel
16
16
  end
17
17
  end
18
18
 
19
+ # A payload already encoded as gzipped JSON. Queued telemetry is kept
20
+ # this way: a full minute is megabytes as Ruby objects but kilobytes
21
+ # compressed.
22
+ Encoded = Struct.new(:bytes)
23
+
24
+ def self.encode(body)
25
+ Encoded.new(gzip(JSON.generate(body)))
26
+ end
27
+
28
+ def self.gzip(string)
29
+ io = StringIO.new
30
+ writer = Zlib::GzipWriter.new(io)
31
+ writer.write(string)
32
+ writer.close
33
+ io.string
34
+ end
35
+
19
36
  def initialize(config)
20
37
  @config = config
21
38
  end
22
39
 
40
+ # body is a Hash, or an Encoded payload sent as is.
23
41
  # :ok (2xx), :retry (network error or 5xx), or :drop (any other status,
24
42
  # including 429 rate limiting, which the agent honors by pausing).
25
43
  def post(path, body)
@@ -29,7 +47,7 @@ module DeployAngel
29
47
  request["Content-Type"] = "application/json"
30
48
  request["Content-Encoding"] = "gzip"
31
49
  request["User-Agent"] = "deployangel-ruby/#{DeployAngel::VERSION} ruby/#{RUBY_VERSION}"
32
- request.body = gzip(JSON.generate(body))
50
+ request.body = (body.is_a?(Encoded) ? body : self.class.encode(body)).bytes
33
51
 
34
52
  response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https",
35
53
  open_timeout: @config.open_timeout, read_timeout: @config.read_timeout,
@@ -54,13 +72,5 @@ module DeployAngel
54
72
  rescue JSON::ParserError
55
73
  {}
56
74
  end
57
-
58
- def gzip(string)
59
- io = StringIO.new
60
- writer = Zlib::GzipWriter.new(io)
61
- writer.write(string)
62
- writer.close
63
- io.string
64
- end
65
75
  end
66
76
  end
@@ -68,12 +68,23 @@ module DeployAngel
68
68
  Array(tasks).filter_map do |key, task|
69
69
  next unless task.is_a?(Hash) && task["schedule"]
70
70
 
71
- { "key" => key.to_s, "class" => task["class"]&.to_s, "schedule" => task["schedule"].to_s, "source" => "solid_queue" }
71
+ { "key" => key.to_s, "class" => task["class"]&.to_s, "schedule" => task["schedule"].to_s, "source" => "solid_queue",
72
+ "time_zone" => scheduler_time_zone }
72
73
  end
73
74
  rescue StandardError
74
75
  []
75
76
  end
76
77
 
78
+ # The zone Solid Queue reads a schedule in when the schedule names
79
+ # none: its own setting, config.time_zone by default. nil when that's
80
+ # the system's local time or Solid Queue predates the setting, and
81
+ # DeployAngel then doesn't assume one.
82
+ def scheduler_time_zone
83
+ ::SolidQueue.time_zone if defined?(::SolidQueue) && ::SolidQueue.respond_to?(:time_zone)
84
+ rescue StandardError
85
+ nil
86
+ end
87
+
77
88
  def file_manifest
78
89
  @file_manifest ||= begin
79
90
  if @config.file_digests
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeployAngel
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deployangel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Owens