puma-plugin-statsd 0.1.0 → 1.2.0

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: 3e2328d05d42da5a0259a1db34e06d577b80640c055601c4689581a9ffdc3d66
4
- data.tar.gz: e69455947064fb8fd3ce30af4d5a97853e0511f0998d11f424924b0fcfe2452f
3
+ metadata.gz: d0b1cd40cd7e6d081b35483c8319f4b1eb3463ded57a1f015e5f78db3771f5a8
4
+ data.tar.gz: 8df635d04448770b411364182f7d6466f736065ce2b4f396b6daec14c2abf364
5
5
  SHA512:
6
- metadata.gz: 885de8a9ccc13c7f1bb95fe160b18c27e204fe71d68fc13e75024cba27ff83f235b1a7876e4b37bb3482987583962eccf7d2cef3d051fba3b48a7c452145bfce
7
- data.tar.gz: 7560d10e03f50ad938408e735026ecec85b8542c79a3e394d1cc8c5250521875c16b5e56e6401776262a6e2ea9aeaafb3b589792b49562c92b0744044a3f8392
6
+ metadata.gz: 892042e3683ca475e062e2439189dd9a17c3fd595eae0e86bb1f687111e6d8f158ff9d8fba034f5fabc8dd066f55289bf0dc0013e2330be59f9bcb764eacfeaa
7
+ data.tar.gz: e2a05da7f70a78c584f34b1b45ca3a8655f20437828be9462d0e83b87c73cb7389ad72081136929c989a34e572601d1bf0208208aa571e29794a7a4f503aab88
@@ -1,5 +1,26 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.2.0 2021-01-07
4
+
5
+ * New metrics: old_workers (PR #[21](https://github.com/yob/puma-plugin-statsd/pull/21)) and requsts_count (PR #[28](https://github.com/yob/puma-plugin-statsd/pull/28))
6
+ * Require json at runtime to be extra sure we don't load the wrong version before bundler has initialised the LOAD_PATH
7
+
8
+ ## 1.1.0 2021-01-03
9
+
10
+ * Assume localhost for statsd host (PR #[20](https://github.com/yob/puma-plugin-statsd/pull/20))
11
+
12
+ ## 1.0.0 2020-11-03
13
+
14
+ * Added option to specify arbitrary datadog tags (PR #[18](https://github.com/yob/puma-plugin-statsd/pull/18))
15
+
16
+ ## 0.3.0 2020-09-24
17
+
18
+ * Support puma 5.x
19
+
20
+ ## 0.2.0 2020-02-29
21
+
22
+ * Added option to prefix stats metric (via STATSD_METRIC_PREFIX env var)
23
+
3
24
  ## 0.1.0 2019-07-06
4
25
 
5
26
  * The statsd port is now configurable
data/README.md CHANGED
@@ -37,12 +37,14 @@ plugin :statsd
37
37
 
38
38
  ## Usage
39
39
 
40
- Ensure you have an environment variable set that points to a statsd host, then boot your puma app as usual. Optionally you may specify a port (default is 8125).
40
+ By default the plugin assumes statsd is available at 127.0.0.1. If that's true in your environment, just start puma like normal:
41
41
 
42
42
  ```
43
- STATSD_HOST=127.0.0.1 bundle exec puma
43
+ bundle exec puma
44
44
  ```
45
45
 
46
+ If statsd isn't on 127.0.0.1 or the port is non-standard, you can configure them using optional environment variables:
47
+
46
48
  ```
47
49
  STATSD_HOST=127.0.0.1 STATSD_PORT=9125 bundle exec puma
48
50
  ```
@@ -53,9 +55,24 @@ metric tags are a non-standard addition to the statsd protocol, supported by
53
55
  the datadog "dogstatsd" server.
54
56
 
55
57
  Should you be reporting the puma metrics to a dogstatsd server, you can set
56
- tags via the following two environment variables.
58
+ tags via the following three environment variables.
59
+
60
+ #### DD_TAGS
61
+
62
+ `DD_TAGS`: Set this to a space-separated list of tags, using the
63
+ [datadog agent standard format](https://docs.datadoghq.com/agent/docker/?tab=standard#global-options).
64
+
65
+ For example, you could set this environment variable to set three datadog tags,
66
+ and then you can filter by in the datadog interface:
57
67
 
58
- `MY_POD_NAME` adds a `pod_name` tag to the metrics. The `MY_POD_NAME`
68
+ ```bash
69
+ export DD_TAGS="env:test simple-tag-0 tag-key-1:tag-value-1"
70
+ bundle exec rails server
71
+ ```
72
+
73
+ #### MY_POD_NAME
74
+
75
+ `MY_POD_NAME`: Set a `pod_name` tag to the metrics. The `MY_POD_NAME`
59
76
  environment variable is recommended in the datadog kubernetes setup
60
77
  documentation, and for puma apps deployed to kubernetes it's very helpful to
61
78
  have the option to report on specific pods.
@@ -70,7 +87,9 @@ env:
70
87
  fieldPath: metadata.name
71
88
  ```
72
89
 
73
- `STATSD_GROUPING` adds a `grouping` tag to the metrics, with a value equal to
90
+ #### STATSD_GROUPING
91
+
92
+ `STATSD_GROUPING`: add a `grouping` tag to the metrics, with a value equal to
74
93
  the environment variable value. This is particularly helpful in a kubernetes
75
94
  deployment where each pod has a unique name but you want the option to group
76
95
  metrics across all pods in a deployment. Setting this on the pods in a
@@ -100,7 +119,7 @@ Start puma:
100
119
  Throw some traffic at it, either with curl or a tool like ab:
101
120
 
102
121
  curl http://127.0.0.1:9292/
103
- ab -n 10000 -c 20 http://127.0.0.1:9292/
122
+ ab -n 10000 -c 20 http://127.0.0.1:9292/
104
123
 
105
124
  Watch the output of the UDP server process - you should see statsd data printed to stdout.
106
125
 
@@ -1,5 +1,4 @@
1
1
  # coding: utf-8, frozen_string_literal: true
2
- require "json"
3
2
  require "puma"
4
3
  require "puma/plugin"
5
4
  require 'socket'
@@ -7,24 +6,18 @@ require 'socket'
7
6
  class StatsdConnector
8
7
  ENV_NAME = "STATSD_HOST"
9
8
  STATSD_TYPES = { count: 'c', gauge: 'g' }
9
+ METRIC_DELIMETER = ".".freeze
10
10
 
11
11
  attr_reader :host, :port
12
12
 
13
13
  def initialize
14
- @host = ENV.fetch(ENV_NAME, nil)
14
+ @host = ENV.fetch(ENV_NAME, "127.0.0.1")
15
15
  @port = ENV.fetch("STATSD_PORT", 8125)
16
16
  end
17
17
 
18
- def enabled?
19
- !!host
20
- end
21
-
22
- def send(metric_name:, value:, type:, tags: {})
18
+ def send(metric_name:, value:, type:, tags: nil)
23
19
  data = "#{metric_name}:#{value}|#{STATSD_TYPES.fetch(type)}"
24
- if tags.any?
25
- tag_str = tags.map { |k,v| "#{k}:#{v}" }.join(",")
26
- data = "#{data}|##{tag_str}"
27
- end
20
+ data = "#{data}|##{tags}" unless tags.nil?
28
21
 
29
22
  socket = UDPSocket.new
30
23
  socket.send(data, 0, host, port)
@@ -40,67 +33,77 @@ class PumaStats
40
33
  end
41
34
 
42
35
  def clustered?
43
- @stats.has_key? "workers"
36
+ @stats.has_key?(:workers)
44
37
  end
45
38
 
46
39
  def workers
47
- @stats.fetch("workers", 1)
40
+ @stats.fetch(:workers, 1)
48
41
  end
49
42
 
50
43
  def booted_workers
51
- @stats.fetch("booted_workers", 1)
44
+ @stats.fetch(:booted_workers, 1)
45
+ end
46
+
47
+ def old_workers
48
+ @stats.fetch(:old_workers, 0)
52
49
  end
53
50
 
54
51
  def running
55
52
  if clustered?
56
- @stats["worker_status"].map { |s| s["last_status"].fetch("running", 0) }.inject(0, &:+)
53
+ @stats[:worker_status].map { |s| s[:last_status].fetch(:running, 0) }.inject(0, &:+)
57
54
  else
58
- @stats.fetch("running", 0)
55
+ @stats.fetch(:running, 0)
59
56
  end
60
57
  end
61
58
 
62
59
  def backlog
63
60
  if clustered?
64
- @stats["worker_status"].map { |s| s["last_status"].fetch("backlog", 0) }.inject(0, &:+)
61
+ @stats[:worker_status].map { |s| s[:last_status].fetch(:backlog, 0) }.inject(0, &:+)
65
62
  else
66
- @stats.fetch("backlog", 0)
63
+ @stats.fetch(:backlog, 0)
67
64
  end
68
65
  end
69
66
 
70
67
  def pool_capacity
71
68
  if clustered?
72
- @stats["worker_status"].map { |s| s["last_status"].fetch("pool_capacity", 0) }.inject(0, &:+)
69
+ @stats[:worker_status].map { |s| s[:last_status].fetch(:pool_capacity, 0) }.inject(0, &:+)
73
70
  else
74
- @stats.fetch("pool_capacity", 0)
71
+ @stats.fetch(:pool_capacity, 0)
75
72
  end
76
73
  end
77
74
 
78
75
  def max_threads
79
76
  if clustered?
80
- @stats["worker_status"].map { |s| s["last_status"].fetch("max_threads", 0) }.inject(0, &:+)
77
+ @stats[:worker_status].map { |s| s[:last_status].fetch(:max_threads, 0) }.inject(0, &:+)
81
78
  else
82
- @stats.fetch("max_threads", 0)
79
+ @stats.fetch(:max_threads, 0)
83
80
  end
84
81
  end
85
- end
86
82
 
87
- Puma::Plugin.create do
88
- # Puma creates the plugin when encountering `plugin` in the config.
89
- def initialize(loader)
90
- @loader = loader
83
+ def requests_count
84
+ if clustered?
85
+ @stats[:worker_status].map { |s| s[:last_status].fetch(:requests_count, 0) }.inject(0, &:+)
86
+ else
87
+ @stats.fetch(:requests_count, 0)
88
+ end
91
89
  end
90
+ end
92
91
 
92
+ Puma::Plugin.create do
93
93
  # We can start doing something when we have a launcher:
94
94
  def start(launcher)
95
95
  @launcher = launcher
96
96
 
97
97
  @statsd = ::StatsdConnector.new
98
- if @statsd.enabled?
99
- @launcher.events.debug "statsd: enabled (host: #{@statsd.host})"
100
- register_hooks
101
- else
102
- @launcher.events.debug "statsd: not enabled (no #{StatsdConnector::ENV_NAME} env var found)"
98
+ @launcher.events.debug "statsd: enabled (host: #{@statsd.host})"
99
+
100
+ # Fetch global metric prefix from env variable
101
+ @metric_prefix = ENV.fetch("STATSD_METRIC_PREFIX", nil)
102
+ if @metric_prefix && !@metric_prefix.end_with?(::StatsdConnector::METRIC_DELIMETER)
103
+ @metric_prefix += ::StatsdConnector::METRIC_DELIMETER
103
104
  end
105
+
106
+ register_hooks
104
107
  end
105
108
 
106
109
  private
@@ -109,34 +112,74 @@ Puma::Plugin.create do
109
112
  in_background(&method(:stats_loop))
110
113
  end
111
114
 
112
- def fetch_stats
113
- JSON.parse(Puma.stats)
115
+ if Puma.respond_to?(:stats_hash)
116
+ def fetch_stats
117
+ Puma.stats_hash
118
+ end
119
+ else
120
+ def fetch_stats
121
+ require "json"
122
+ stats = Puma.stats
123
+ JSON.parse(stats, symbolize_names: true)
124
+ end
114
125
  end
115
126
 
116
- def tags
117
- tags = {}
127
+ def environment_variable_tags
128
+ # Tags are separated by spaces, and while they are normally a tag and
129
+ # value separated by a ':', they can also just be tagged without any
130
+ # associated value.
131
+ #
132
+ # Examples: simple-tag-0 tag-key-1:tag-value-1
133
+ #
134
+ tags = []
135
+
118
136
  if ENV.has_key?("MY_POD_NAME")
119
- tags[:pod_name] = ENV.fetch("MY_POD_NAME", "no_pod")
137
+ tags << "pod_name:#{ENV['MY_POD_NAME']}"
120
138
  end
139
+
121
140
  if ENV.has_key?("STATSD_GROUPING")
122
- tags[:grouping] = ENV.fetch("STATSD_GROUPING", "no-group")
141
+ tags << "grouping:#{ENV['STATSD_GROUPING']}"
123
142
  end
124
- tags
143
+
144
+ # Standardised datadog tag attributes, so that we can share the metric
145
+ # tags with the application running
146
+ #
147
+ # https://docs.datadoghq.com/agent/docker/?tab=standard#global-options
148
+ #
149
+ if ENV.has_key?("DD_TAGS")
150
+ ENV["DD_TAGS"].split(/\s+/).each do |t|
151
+ tags << t
152
+ end
153
+ end
154
+
155
+ # Return nil if we have no environment variable tags. This way we don't
156
+ # send an unnecessary '|' on the end of each stat
157
+ return nil if tags.empty?
158
+
159
+ tags.join(",")
160
+ end
161
+
162
+ def prefixed_metric_name(puma_metric)
163
+ "#{@metric_prefix}#{puma_metric}"
125
164
  end
126
165
 
127
166
  # Send data to statsd every few seconds
128
167
  def stats_loop
168
+ tags = environment_variable_tags
169
+
129
170
  sleep 5
130
171
  loop do
131
172
  @launcher.events.debug "statsd: notify statsd"
132
173
  begin
133
174
  stats = ::PumaStats.new(fetch_stats)
134
- @statsd.send(metric_name: "puma.workers", value: stats.workers, type: :gauge, tags: tags)
135
- @statsd.send(metric_name: "puma.booted_workers", value: stats.booted_workers, type: :gauge, tags: tags)
136
- @statsd.send(metric_name: "puma.running", value: stats.running, type: :gauge, tags: tags)
137
- @statsd.send(metric_name: "puma.backlog", value: stats.backlog, type: :gauge, tags: tags)
138
- @statsd.send(metric_name: "puma.pool_capacity", value: stats.pool_capacity, type: :gauge, tags: tags)
139
- @statsd.send(metric_name: "puma.max_threads", value: stats.max_threads, type: :gauge, tags: tags)
175
+ @statsd.send(metric_name: prefixed_metric_name("puma.workers"), value: stats.workers, type: :gauge, tags: tags)
176
+ @statsd.send(metric_name: prefixed_metric_name("puma.booted_workers"), value: stats.booted_workers, type: :gauge, tags: tags)
177
+ @statsd.send(metric_name: prefixed_metric_name("puma.old_workers"), value: stats.old_workers, type: :gauge, tags: tags)
178
+ @statsd.send(metric_name: prefixed_metric_name("puma.running"), value: stats.running, type: :gauge, tags: tags)
179
+ @statsd.send(metric_name: prefixed_metric_name("puma.backlog"), value: stats.backlog, type: :gauge, tags: tags)
180
+ @statsd.send(metric_name: prefixed_metric_name("puma.pool_capacity"), value: stats.pool_capacity, type: :gauge, tags: tags)
181
+ @statsd.send(metric_name: prefixed_metric_name("puma.max_threads"), value: stats.max_threads, type: :gauge, tags: tags)
182
+ @statsd.send(metric_name: prefixed_metric_name("puma.requests_count"), value: stats.requests_count, type: :gauge, tags: tags)
140
183
  rescue StandardError => e
141
184
  @launcher.events.error "! statsd: notify stats failed:\n #{e.to_s}\n #{e.backtrace.join("\n ")}"
142
185
  ensure
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma-plugin-statsd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Healy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-06 00:00:00.000000000 Z
11
+ date: 2021-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: puma
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '3.12'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '5'
22
+ version: '6'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: '3.12'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '5'
32
+ version: '6'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: json
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -143,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
143
143
  - !ruby/object:Gem::Version
144
144
  version: '0'
145
145
  requirements: []
146
- rubygems_version: 3.0.1
146
+ rubygems_version: 3.0.3
147
147
  signing_key:
148
148
  specification_version: 4
149
149
  summary: Send puma metrics to statsd via a background thread