process_metrics 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 60c71413069134039aeacc1348863efe45184b24
4
+ data.tar.gz: ec20a17b8047721208c0ac6b3135f424b3df31f5
5
+ SHA512:
6
+ metadata.gz: 638f0704e0ba772963b5f72f36773a46d8661750d232ddbc979afa5facbb34e0f29982da40ab7759971398da4052ca17267ff1c088f85ab688459074340e97e9
7
+ data.tar.gz: b10606a34d866cca1abaa4d78c94c4e156ce824f4f5735d54ebd2a645a00885f5b68902e00e62e86ed85900d02f2c34821206585d837108d7e93d2d2aa58127a
data/README.md ADDED
@@ -0,0 +1,142 @@
1
+ [![Code Climate](https://codeclimate.com/github/lucasuyezu/metric.png)](https://codeclimate.com/github/lucasuyezu/metric)
2
+
3
+ Process Metrics
4
+ ===============
5
+
6
+ This project aims to collect metrics for processes.
7
+
8
+ It differs from other metric gems by measuring **processes** instead of code execution.
9
+
10
+ A process can begin in one point in time and finish in another thread, or even another server.
11
+
12
+ Features
13
+ ========
14
+
15
+ - Sub-process: Start a process, track each individual step and see the global metric. This is similar to how web browsers show you in the Network view how much time each asset takes to load. See where the holes are.
16
+ - Unobtrusive: Failure in saving a metric will not break your code unless you use `Metric.measure!`, `Metric#find!` and `Metric#finish!`
17
+ - Assyncronous: Persistence is made in parallel, so measurement itself will not affect the results.
18
+ - Multiple data stores: ActiveRecord for now. Leela2 in the near future. Implement your own if you want/need.
19
+
20
+ Usage
21
+ =====
22
+
23
+ Manual
24
+ ------
25
+
26
+ ### Single process
27
+
28
+ ```ruby
29
+ ProcessMetrics.measure('name') do |metric|
30
+ do_work
31
+ metric.data = { key: 'value' } # optional
32
+ end
33
+ ```
34
+
35
+ ### Sub Process in the same block
36
+ ```ruby
37
+ ProcessMetrics.measure('name') do |metric|
38
+ metric.measure('first sub process') do |sub_metric|
39
+ do_work
40
+ sub_metric.data = { key: 'value' } # optional
41
+ end
42
+
43
+ do_more_work
44
+
45
+ metric.measure('second sub process') do |sub_metric|
46
+ do_even_more_work
47
+ sub_metric.data = { key: 'value' } # optional
48
+ end
49
+ end
50
+ ```
51
+
52
+ ### Sub Processes in different places
53
+ ```ruby
54
+ # in app/controller/my_controller.rb
55
+ metric = ProcessMetrics.new 'name' # metric objects have an uuid
56
+ save_metric(metric.uuid)
57
+ schedule_worker
58
+
59
+ # in lib/workers/my_worker.rb
60
+ ProcessMetrics.find(metric_uuid).measure('sub process') do |sub_metric|
61
+ do_work
62
+ submetric.data = { key: 'value' } # optional
63
+ end
64
+ ```
65
+
66
+ Automatic
67
+ ---------
68
+
69
+ ### Single process
70
+ ```ruby
71
+ class MyModel
72
+ include ProcessMetrics::Timer
73
+ measure :perform,
74
+ name: -> { "metric name" } # Default would be "MyModel#perform"
75
+
76
+ def perform
77
+ do_some_work
78
+ end
79
+ end
80
+ ```
81
+
82
+ ### Sub processes
83
+ ```ruby
84
+ class MyWorker
85
+ include ProcessMetrics::Timer
86
+ measure :perform,
87
+ parent_metric_uuid: -> { "2d931510-d99f-494a-8c67-87feb05e1594" }
88
+
89
+ belongs_to :my_model
90
+
91
+ def perform
92
+ do_some_work
93
+ end
94
+ end
95
+ ```
96
+
97
+ Persistence
98
+ ===========
99
+
100
+ Data collected will be serialized into a json and persisted.
101
+ You can put any data you like into the data field and it will be serialized as well.
102
+
103
+
104
+ ```json
105
+ {
106
+ "metrics": [{
107
+ "uuid": "bad85eb9-0713-4da7-8d36-07a8e4b00eab",
108
+ "name": "virtual_machine_provisioning",
109
+ "started_at": "2014-03-13T11:34:53-03:00",
110
+ "finished_at": "2014-03-13T11:35:14-03:00",
111
+ "data": {
112
+ "customer_login": "anon",
113
+ "provisioning_id": 6000123456,
114
+ "object_type": "virtual_machine",
115
+ "object_id": "cpro123456"
116
+ },
117
+ "links": {
118
+ "metrics": [
119
+ "2d931510-d99f-494a-8c67-87feb05e1594",
120
+ "62936e70-1815-439b-bf89-8492855a7e6b"
121
+ ]
122
+ }
123
+ }]
124
+ }
125
+ ```
126
+
127
+ Configuration
128
+ =============
129
+
130
+ One time, in a migration
131
+ ------------------------
132
+ ```ruby
133
+ ProcessMetrics::Persistence::ActiveRecord.setup!
134
+ ```
135
+
136
+ Then, in an initializer
137
+ -----------------------
138
+
139
+ ```ruby
140
+ ProcessMetrics.logger = Rails.logger
141
+ ProcessMetrics.persistence = Metric::Persistence::ActiveRecord # Default is Logger
142
+ ```
@@ -0,0 +1,57 @@
1
+ require 'securerandom'
2
+
3
+ module ProcessMetrics
4
+ class Base
5
+ attr_accessor :data, :uuid
6
+
7
+ def initialize(name, parent=nil)
8
+ @uuid = SecureRandom.uuid
9
+ @parent_uuid = parent ? parent.uuid : nil
10
+ @data = nil
11
+ @name = name
12
+ @started_at = Time.now
13
+ @finished_at = nil
14
+ end
15
+
16
+ def attributes
17
+ {
18
+ uuid: @uuid,
19
+ parent_uuid: @parent_uuid,
20
+ name: @name,
21
+ data: @data,
22
+ started_at: @started_at,
23
+ finished_at: @finished_at
24
+ }
25
+ end
26
+
27
+ def serialized_attributes
28
+ {
29
+ uuid: @uuid,
30
+ parent_uuid: @parent_uuid,
31
+ name: @name,
32
+ data: @data,
33
+ started_at: @started_at.strftime("%Y-%m-%d %H:%M:%S.%N"),
34
+ finished_at: @finished_at.strftime("%Y-%m-%d %H:%M:%S.%N")
35
+ }
36
+ end
37
+
38
+ def save
39
+ @finished_at = Time.now
40
+ ProcessMetrics.config.persistence.save(self)
41
+ end
42
+
43
+ def measure(name, &block)
44
+ work(name, self, &block)
45
+ end
46
+
47
+ def self.work(name, parent=nil, &block)
48
+ metric = ProcessMetrics::Base.new name, parent
49
+ block.call metric
50
+ metric.save
51
+ end
52
+
53
+ def to_s
54
+ self.class.name + " " + serialized_attributes.to_s
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,18 @@
1
+ require 'hashie'
2
+ require 'logger'
3
+ require 'process_metrics/persistence'
4
+
5
+ module ProcessMetrics
6
+ class Config < Hashie::Mash
7
+ def initialize
8
+ self.logger = Logger.new("metric.log")
9
+ self.persistence = ProcessMetrics::Persistence::Logger
10
+ self.active_record = Hashie::Mash.new
11
+ self.active_record.table_name = 'metrics'
12
+ end
13
+ end
14
+
15
+ def self.config
16
+ Config.new
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ module ProcessMetrics
2
+ module Persistence
3
+ module Logger
4
+ def self.save metric
5
+ ProcessMetrics.logger.info(metric.to_s)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1 @@
1
+ require 'process_metrics/persistence/logger'
@@ -0,0 +1,71 @@
1
+ module ClassMethods
2
+ @@replacing = false
3
+
4
+ def measure(*methods_names)
5
+ options = extract_options(methods_names)
6
+
7
+ methods_names.each do |method_name|
8
+ measure_method_name = :"measure_#{method_name}"
9
+ raw_method_name = :"raw_#{method_name}"
10
+ self.send(:define_method, measure_method_name) do |*args, &block|
11
+ parent_uuid = parent_uuid(options)
12
+ process_name = "#{self.class.name}##{method_name}"
13
+ parent = ProcessMetrics::Base.new(process_name)
14
+ parent.uuid = parent_uuid
15
+
16
+ ProcessMetrics.measure(process_name, parent) do |metric|
17
+ ProcessMetrics.logger.debug "About to send #{raw_method_name} with args #{args.inspect} and block #{block.inspect} to #{self}"
18
+ metric.data = {args: args, block: block}
19
+
20
+ send(raw_method_name, *args, &block)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def method_added(method_name)
27
+ return if @@replacing || method_name =~ /^measure_/ || method_name =~ /^raw_/
28
+ ProcessMetrics.logger.debug "Adding method #{method_name} in #{self}"
29
+
30
+ measure_method_name = :"measure_#{method_name}"
31
+ raw_method_name = :"raw_#{method_name}"
32
+
33
+ if self.instance_methods.include? measure_method_name
34
+ ProcessMetrics.logger.debug "#{self}##{measure_method_name} exists. Replacing..."
35
+ @@replacing = true
36
+ alias_method raw_method_name, method_name
37
+ alias_method method_name, measure_method_name
38
+ @@replacing = false
39
+ else
40
+ ProcessMetrics.logger.debug "#{self}##{measure_method_name} does not exist. Exiting..."
41
+ end
42
+ end
43
+
44
+ private
45
+ def extract_options(array)
46
+ array.last && array.last.is_a?(Hash) ? array.pop : nil
47
+ end
48
+ end
49
+
50
+ module ProcessMetrics
51
+ module Timer
52
+ def self.included(base)
53
+ base.extend ClassMethods
54
+ end
55
+
56
+ def parent_uuid(options)
57
+ return unless options
58
+
59
+ if options[:parent_uuid].respond_to?(:call)
60
+ # It's a proc. parent_uuid is the return value
61
+ options[:parent_uuid].call
62
+ elsif options[:parent_uuid].is_a?(Symbol)
63
+ # It's a symbol. Call method on object and parent_uuid is its result.
64
+ send(options[:parent_uuid])
65
+ else
66
+ # It's something static. Just call #to_s
67
+ options[:parent_uuid].to_s
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,3 @@
1
+ module ProcessMetrics
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,18 @@
1
+ require 'process_metrics/base'
2
+ require 'process_metrics/config'
3
+ require 'process_metrics/version'
4
+ require 'process_metrics/timer'
5
+
6
+ module ProcessMetrics
7
+ def self.logger
8
+ config.logger
9
+ end
10
+
11
+ def self.logger=(logger)
12
+ config.logger = logger
13
+ end
14
+
15
+ def self.measure(name, parent=nil, &block)
16
+ ProcessMetrics::Base.work(name, parent, &block)
17
+ end
18
+ end
@@ -0,0 +1,151 @@
1
+ require 'minitest/autorun'
2
+
3
+ require 'process_metrics'
4
+
5
+ class MyClass
6
+ include ProcessMetrics::Timer
7
+ measure :do_simple_work,
8
+ :do_work_with_params,
9
+ :do_work_with_block,
10
+ :do_work_with_params_and_block
11
+
12
+ attr_accessor :my_var
13
+
14
+ def initialize(my_var)
15
+ @my_var = my_var
16
+ end
17
+
18
+ def do_simple_work
19
+ @my_var += 1
20
+ end
21
+
22
+ def do_work_with_block(&block)
23
+ block.call(self)
24
+ end
25
+
26
+ def do_work_with_params(user_id)
27
+ @my_var += user_id + 3
28
+ end
29
+
30
+ def do_work_with_params_and_block(user_id, &block)
31
+ @my_var += user_id + 5
32
+ block.call self
33
+ end
34
+ end
35
+
36
+
37
+ class MyChildSymbolClass
38
+ include ProcessMetrics::Timer
39
+ measure :do_simple_work,
40
+ parent_uuid: :parent_object_uuid
41
+
42
+ attr_accessor :my_var
43
+
44
+ def initialize(my_var)
45
+ @my_var = my_var
46
+ end
47
+
48
+ def do_simple_work
49
+ @my_var += 1
50
+ end
51
+
52
+ def parent_object_uuid
53
+ "fe51cfe1-114f-4516-8c5f-e488e48c3778"
54
+ end
55
+ end
56
+
57
+ class MyChildProcClass
58
+ include ProcessMetrics::Timer
59
+ measure :do_simple_work,
60
+ parent_uuid: -> { "b0d50400-cae0-4cc4-b289-f487d3c9b397" }
61
+
62
+ attr_accessor :my_var
63
+
64
+ def initialize(my_var)
65
+ @my_var = my_var
66
+ end
67
+
68
+ def do_simple_work
69
+ @my_var += 1
70
+ end
71
+ end
72
+
73
+ class MyChildStringClass
74
+ include ProcessMetrics::Timer
75
+ measure :do_simple_work,
76
+ parent_uuid: "c9b104fb-3389-4c21-b0e9-6c88fd905e4f"
77
+
78
+ attr_accessor :my_var
79
+
80
+ def initialize(my_var)
81
+ @my_var = my_var
82
+ end
83
+
84
+ def do_simple_work
85
+ @my_var += 1
86
+ end
87
+ end
88
+
89
+ class TestBlock < Minitest::Test
90
+ def test_simple_method
91
+ my_object = MyClass.new(1)
92
+ my_object.do_simple_work
93
+
94
+ assert_equal 2, my_object.my_var
95
+ end
96
+
97
+ def test_method_with_params
98
+ my_object = MyClass.new(2)
99
+ my_object.do_work_with_params(3)
100
+
101
+ assert_equal 8, my_object.my_var
102
+ end
103
+
104
+ def test_method_with_block
105
+ my_object = MyClass.new(5)
106
+
107
+ my_object.do_work_with_block do |that_object|
108
+ that_object.my_var += 7
109
+ end
110
+
111
+ assert_equal 12, my_object.my_var
112
+ end
113
+
114
+ def test_method_with_params_and_block
115
+ my_object = MyClass.new(11)
116
+
117
+ my_object.do_work_with_params_and_block(13) do |that_object|
118
+ that_object.my_var += 17
119
+ end
120
+
121
+ assert_equal 46, my_object.my_var
122
+ end
123
+
124
+ def test_child_symbol_simple_method
125
+ my_object = MyChildSymbolClass.new(1)
126
+ my_object.do_simple_work
127
+
128
+ assert_equal 2, my_object.my_var
129
+ end
130
+
131
+ def test_child_proc_simple_method
132
+ my_object = MyChildProcClass.new(1)
133
+ my_object.do_simple_work
134
+
135
+ assert_equal 2, my_object.my_var
136
+ end
137
+
138
+ def test_child_string_simple_method
139
+ my_object = MyChildStringClass.new(1)
140
+ my_object.do_simple_work
141
+
142
+ assert_equal 2, my_object.my_var
143
+ end
144
+
145
+ def test_block
146
+ ProcessMetrics.measure('anon') do |metric|
147
+ sleep rand
148
+ metric.data = { my_key: 'my_value'}
149
+ end
150
+ end
151
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: process_metrics
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Lucas Uyezu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hashie
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.5
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '2.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.5
33
+ - !ruby/object:Gem::Dependency
34
+ name: pry-debugger
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.2'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.2.2
43
+ type: :development
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '0.2'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 0.2.2
53
+ - !ruby/object:Gem::Dependency
54
+ name: minitest
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '5.3'
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 5.3.1
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '5.3'
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 5.3.1
73
+ description: A gem that provides utilities to measure a process and its sub tasks.
74
+ email: lucas@uyezu.com
75
+ executables: []
76
+ extensions: []
77
+ extra_rdoc_files:
78
+ - README.md
79
+ files:
80
+ - README.md
81
+ - lib/process_metrics.rb
82
+ - lib/process_metrics/base.rb
83
+ - lib/process_metrics/config.rb
84
+ - lib/process_metrics/persistence.rb
85
+ - lib/process_metrics/persistence/logger.rb
86
+ - lib/process_metrics/timer.rb
87
+ - lib/process_metrics/version.rb
88
+ - test/test_simple.rb
89
+ homepage: https://github.com/lucasuyezu/process_metrics
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options:
95
+ - "--charset=UTF-8"
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 2.2.2
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: Process metrics
114
+ test_files:
115
+ - test/test_simple.rb