image_vise 0.4.0 → 0.5.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 +5 -5
- data/.travis.yml +5 -4
- data/examples/{error_handline_appsignal.rb → error_handling_appsignal.rb} +1 -1
- data/image_vise.gemspec +1 -1
- data/lib/image_vise/image_request.rb +3 -0
- data/lib/image_vise/pipeline.rb +4 -1
- data/lib/image_vise/render_engine.rb +7 -2
- data/lib/image_vise/version.rb +1 -1
- data/lib/image_vise.rb +9 -3
- data/lib/measurometer.rb +92 -0
- data/spec/measurometer_spec.rb +58 -0
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b65aacfbdbada4bb3d52c9883955ff2204ee84aa66bc82d05fc81320259ac900
|
4
|
+
data.tar.gz: d8576cfba12f8793a75dc36f8a5c2c206f7650f402d048432f763de82ddeb0b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 767f28b82f290cfefdf023f83a6794901744d585eff9f0f2af92a6c408718b4663162156270d51f0ea7def4de2e14339a0bc0183b80ba62d4d16f11b69359e11
|
7
|
+
data.tar.gz: bb8534e292d19e51015b24586d0db5c472da2b02c3463180c35f42d8cff1d2d39aca875d5446255ea5b67b1bf59f849dda08065792438324a6ed3c9a753d9549
|
data/.travis.yml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Anywhere in your app code
|
2
2
|
module ImageViseAppsignal
|
3
3
|
ImageVise::RenderEngine.prepend self
|
4
|
-
|
4
|
+
ImageVise::Measurometer.drivers << Appsignal # to obtain ImageVise instrumentation
|
5
5
|
def setup_error_handling(rack_env)
|
6
6
|
txn = Appsignal::Transaction.current
|
7
7
|
txn.set_action('%s#%s' % [self.class, 'call'])
|
data/image_vise.gemspec
CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
|
|
21
21
|
else
|
22
22
|
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
spec.files = `git ls-files -z`.split("\x0")
|
26
26
|
spec.bindir = "exe"
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
@@ -14,9 +14,12 @@ class ImageVise::ImageRequest < Ks.strict(:src_url, :pipeline)
|
|
14
14
|
|
15
15
|
# Check the signature before decoding JSON (since we will be creating symbols)
|
16
16
|
unless valid_signature?(base64_encoded_params, given_signature, secrets)
|
17
|
+
ImageVise::Measurometer.increment_counter('image_vise.params.invalid_signatures', 1)
|
17
18
|
raise SignatureError, "Invalid or missing signature"
|
18
19
|
end
|
19
20
|
|
21
|
+
ImageVise::Measurometer.increment_counter('image_vise.params.valid_signatures', 1)
|
22
|
+
|
20
23
|
# Decode the JSON - only AFTER the signature has been validated,
|
21
24
|
# so we can use symbol keys
|
22
25
|
decoded_json = Base64.decode64(base64_encoded_params)
|
data/lib/image_vise/pipeline.rb
CHANGED
@@ -46,7 +46,10 @@ class ImageVise::Pipeline
|
|
46
46
|
|
47
47
|
def apply!(magick_image, image_metadata)
|
48
48
|
@ops.each do |operator|
|
49
|
-
|
49
|
+
operator_short_classname = operator.class.to_s.split('::').pop
|
50
|
+
ImageVise::Measurometer.instrument('image_vise.op.%s' % operator_short_classname) do
|
51
|
+
apply_operator_passing_metadata(magick_image, operator, image_metadata)
|
52
|
+
end
|
50
53
|
end
|
51
54
|
end
|
52
55
|
|
@@ -283,7 +283,10 @@
|
|
283
283
|
render_file_type = source_file_type
|
284
284
|
|
285
285
|
# Load the first frame of the animated GIF _or_ the blended compatibility layer from Photoshop
|
286
|
-
image_list =
|
286
|
+
image_list = ImageVise::Measurometer.instrument('image_vise.load_pixbuf') do
|
287
|
+
Magick::Image.read(source_file_path)
|
288
|
+
end
|
289
|
+
|
287
290
|
magick_image = image_list.first # Picks up the "precomp" PSD layer in compatibility mode, or the first frame of a GIF
|
288
291
|
|
289
292
|
# If any operators want to stash some data for downstream use we use this Hash
|
@@ -297,7 +300,9 @@
|
|
297
300
|
# it so that we get a KeyError if some operator has deleted it without providing a replacement.
|
298
301
|
# If no operators touched the writer we are going to use the automatic format selection
|
299
302
|
writer = metadata.fetch(:writer, ImageVise::AutoWriter.new)
|
300
|
-
|
303
|
+
ImageVise::Measurometer.instrument('image_vise.write_image') do
|
304
|
+
writer.write_image!(magick_image, metadata, render_to_path)
|
305
|
+
end
|
301
306
|
|
302
307
|
# Another metadata element is the expire_after, which we default to an app-wide setting
|
303
308
|
metadata.fetch(:expire_after_seconds, ImageVise.cache_lifetime_seconds)
|
data/lib/image_vise/version.rb
CHANGED
data/lib/image_vise.rb
CHANGED
@@ -9,8 +9,10 @@ require 'rack'
|
|
9
9
|
|
10
10
|
class ImageVise
|
11
11
|
require_relative 'image_vise/version'
|
12
|
+
|
12
13
|
S_MUTEX = Mutex.new
|
13
14
|
private_constant :S_MUTEX
|
15
|
+
|
14
16
|
# The default cache liftime is 30 days, and will be used if no custom lifetime is set.
|
15
17
|
DEFAULT_CACHE_LIFETIME = 2_592_000
|
16
18
|
|
@@ -158,7 +160,9 @@ class ImageVise
|
|
158
160
|
return unless maybe_image
|
159
161
|
return unless maybe_image.respond_to?(:destroy!)
|
160
162
|
return if maybe_image.destroyed?
|
161
|
-
|
163
|
+
ImageVise::Measurometer.instrument('image_vise.image_destroy_dealloc') do
|
164
|
+
maybe_image.destroy!
|
165
|
+
end
|
162
166
|
end
|
163
167
|
|
164
168
|
# Used as a shorthand to force-dealloc Tempfiles in an ensure() blocks. Since
|
@@ -166,8 +170,10 @@ class ImageVise
|
|
166
170
|
# in scope but not yet set to an image) we take the possibility of nils into account.
|
167
171
|
def self.close_and_unlink(maybe_tempfile)
|
168
172
|
return unless maybe_tempfile
|
169
|
-
|
170
|
-
|
173
|
+
ImageVise::Measurometer.instrument('image_vise.tempfile_unlink') do
|
174
|
+
maybe_tempfile.close unless maybe_tempfile.closed?
|
175
|
+
maybe_tempfile.unlink
|
176
|
+
end
|
171
177
|
end
|
172
178
|
end
|
173
179
|
|
data/lib/measurometer.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Measurometer is 1-1 API compatible with Appsignal,
|
2
|
+
# which we use a lot
|
3
|
+
class ImageVise::Measurometer
|
4
|
+
class << self
|
5
|
+
# Permits adding instrumentation drivers. To magically
|
6
|
+
# obtain all Appsignal instrumentation, add the Appsignal module
|
7
|
+
# as a driver.
|
8
|
+
#
|
9
|
+
# Measurometer.drivers << Appsignal
|
10
|
+
#
|
11
|
+
# A driver must be reentrant and thread-safe - it should be possible
|
12
|
+
# to have multiple `instrument` calls open from different threads at the
|
13
|
+
# same time.
|
14
|
+
#
|
15
|
+
# The driver must support the same interface as the Measurometer class
|
16
|
+
# itself, minus the `drivers` and `instrument_instance_method` methods.
|
17
|
+
#
|
18
|
+
# @return Array
|
19
|
+
def drivers
|
20
|
+
@drivers ||= []
|
21
|
+
@drivers
|
22
|
+
end
|
23
|
+
|
24
|
+
# Runs a given block within a cascade of `instrument` blocks of all the
|
25
|
+
# added drivers.
|
26
|
+
#
|
27
|
+
# Measurometer.instrument('do_foo') { compute! }
|
28
|
+
#
|
29
|
+
# unfolds to
|
30
|
+
#
|
31
|
+
# Appsignal.instrument('do_foo') do
|
32
|
+
# Statsd.timing('do_foo') do
|
33
|
+
# compute!
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# Note that it is _imperative_ that the block return value is preserved
|
38
|
+
# by the drivers and passed as the result of the block.
|
39
|
+
#
|
40
|
+
# @param block_name[String] under which path to push the metric
|
41
|
+
# @param blk[#call] the block to instrument
|
42
|
+
# @return [Object] the return value of &blk
|
43
|
+
def instrument(block_name, &blk)
|
44
|
+
return yield unless @drivers && @drivers.any? # The block wrapping business is not free
|
45
|
+
@drivers.inject(blk) { |outer_block, driver|
|
46
|
+
-> {
|
47
|
+
driver.instrument(block_name, &outer_block)
|
48
|
+
}
|
49
|
+
}.call
|
50
|
+
end
|
51
|
+
|
52
|
+
# Adds a distribution value (sample) under a given path
|
53
|
+
#
|
54
|
+
# @param value_path[String] under which path to push the metric
|
55
|
+
# @param value[Numeric] distribution value
|
56
|
+
# @return nil
|
57
|
+
def add_distribution_value(value_path, value)
|
58
|
+
(@drivers || []).each { |d| d.add_distribution_value(value_path, value) }
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# Increment a named counter under a given path
|
63
|
+
#
|
64
|
+
# @param counter_path[String] under which path to push the metric
|
65
|
+
# @param by[Integer] the counter increment to apply
|
66
|
+
# @return nil
|
67
|
+
def increment_counter(counter_path, by)
|
68
|
+
(@drivers || []).each { |d| d.increment_counter(counter_path, by) }
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# Wrap an anonymous module around an instance method in the given class to have
|
73
|
+
# it instrumented automatically. The name of the measurement will be interpolated as:
|
74
|
+
#
|
75
|
+
# "#{prefix}.#{rightmost_class_constant_name}.#{instance_method_name}"
|
76
|
+
#
|
77
|
+
# @param target_class[Class] the class to instrument
|
78
|
+
# @param instance_method_name_to_instrument[Symbol] the method name to instrument
|
79
|
+
# @param path_prefix[String] under which path to push the instrumented metric
|
80
|
+
# @return void
|
81
|
+
def instrument_instance_method(target_class, instance_method_name_to_instrument, path_prefix)
|
82
|
+
short_class_name = target_class.to_s.split('::').last
|
83
|
+
instrumentation_name = [path_prefix, short_class_name, instance_method_name_to_instrument].join('.')
|
84
|
+
instrumenter_module = Module.new do
|
85
|
+
define_method(instance_method_name_to_instrument) do |*any|
|
86
|
+
::ImageVise::Measurometer.instrument(instrumentation_name) { super(*any) }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
target_class.prepend(instrumenter_module)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ImageVise::Measurometer do
|
4
|
+
RSpec::Matchers.define :include_counter_or_measurement_named do |named|
|
5
|
+
match do |actual|
|
6
|
+
actual.any? do |e|
|
7
|
+
e[0] == named && e[1] > 0
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'instruments a full cycle FormatParser.parse' do
|
13
|
+
driver_class = Class.new do
|
14
|
+
attr_accessor :timings, :counters, :distributions
|
15
|
+
def initialize
|
16
|
+
@timings = []
|
17
|
+
@distributions = []
|
18
|
+
@counters = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def instrument(block_name)
|
22
|
+
s = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
23
|
+
yield.tap do
|
24
|
+
delta = Process.clock_gettime(Process::CLOCK_MONOTONIC) - s
|
25
|
+
@timings << [block_name, delta * 1000]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_distribution_value(value_path, value)
|
30
|
+
@distributions << [value_path, value]
|
31
|
+
end
|
32
|
+
|
33
|
+
def increment_counter(value_path, value)
|
34
|
+
@counters << [value_path, value]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
instrumenter = driver_class.new
|
39
|
+
described_class.drivers << instrumenter
|
40
|
+
|
41
|
+
builder = ImageVise::Pipeline.new
|
42
|
+
pipeline = builder.
|
43
|
+
auto_orient.
|
44
|
+
fit_crop(width: 48, height: 48, gravity: 'c').
|
45
|
+
sharpen(radius: 2, sigma: 0.5).
|
46
|
+
ellipse_stencil.
|
47
|
+
strip_metadata
|
48
|
+
|
49
|
+
image = Magick::Image.read(test_image_path)[0]
|
50
|
+
pipeline.apply! image, {}
|
51
|
+
|
52
|
+
described_class.drivers.delete(instrumenter)
|
53
|
+
expect(described_class.drivers).not_to include(instrumenter)
|
54
|
+
|
55
|
+
expect(instrumenter.timings).to include_counter_or_measurement_named('image_vise.op.AutoOrient')
|
56
|
+
expect(instrumenter.timings).to include_counter_or_measurement_named('image_vise.op.StripMetadata')
|
57
|
+
end
|
58
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: image_vise
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik Tarkhanov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: patron
|
@@ -215,7 +215,7 @@ files:
|
|
215
215
|
- SECURITY.md
|
216
216
|
- examples/config.ru
|
217
217
|
- examples/custom_image_operator.rb
|
218
|
-
- examples/
|
218
|
+
- examples/error_handling_appsignal.rb
|
219
219
|
- examples/error_handling_sentry.rb
|
220
220
|
- image_vise.gemspec
|
221
221
|
- lib/image_vise.rb
|
@@ -240,6 +240,7 @@ files:
|
|
240
240
|
- lib/image_vise/version.rb
|
241
241
|
- lib/image_vise/writers/auto_writer.rb
|
242
242
|
- lib/image_vise/writers/jpeg_writer.rb
|
243
|
+
- lib/measurometer.rb
|
243
244
|
- spec/image_vise/auto_orient_spec.rb
|
244
245
|
- spec/image_vise/background_fill_spec.rb
|
245
246
|
- spec/image_vise/crop_spec.rb
|
@@ -261,6 +262,7 @@ files:
|
|
261
262
|
- spec/image_vise/writers/jpeg_writer_spec.rb
|
262
263
|
- spec/image_vise_spec.rb
|
263
264
|
- spec/layers-with-blending.psd
|
265
|
+
- spec/measurometer_spec.rb
|
264
266
|
- spec/spec_helper.rb
|
265
267
|
- spec/test_server.rb
|
266
268
|
- spec/waterside_magic_hour.jpg
|
@@ -290,7 +292,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
290
292
|
version: '0'
|
291
293
|
requirements: []
|
292
294
|
rubyforge_project:
|
293
|
-
rubygems_version: 2.
|
295
|
+
rubygems_version: 2.7.3
|
294
296
|
signing_key:
|
295
297
|
specification_version: 4
|
296
298
|
summary: Runtime thumbnailing proxy
|