dogstatsd 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +89 -0
  4. data/lib/dogstatsd.rb +377 -0
  5. metadata +105 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4541fbd03602c695372479982c0e530ca2bb8d1c
4
+ data.tar.gz: c7ce14813d97c3077695d73bd31ee33de178ed4c
5
+ SHA512:
6
+ metadata.gz: 7142447c162ad59a7737dc90b748ea3fb6eb27c8a03d17155bdcc8673ef20988e2f4aa5a9f736696182f1ee6e0dc383a8ace632b85da144321800cf9ebca4c18
7
+ data.tar.gz: cc2250c86c1688d31016fd0175674316321ee3e26c89cd377b7c69ad22180f90951ad65e3640f52742fea21dc5033d2d9ec496926f5e43d62af3b0cd2ddd861a
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Rein Henrichs
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,89 @@
1
+
2
+ dogstatsd
3
+ =========
4
+
5
+ A client for DogStatsD, an extension of the StatsD metric server for Datadog.
6
+
7
+ [![Build Status](https://secure.travis-ci.org/DataDog/dogstatsd-ruby.png)](http://travis-ci.org/DataDog/dogstatsd-ruby)
8
+
9
+ Quick Start Guide
10
+ -----------------
11
+
12
+ First install the library:
13
+
14
+ gem install dogstatsd
15
+
16
+ Then start instrumenting your code:
17
+
18
+ ``` ruby
19
+ # Load the dogstats module.
20
+ require 'dogstatsd'
21
+
22
+ # Create a stats instance.
23
+ statsd = Dogstatsd.new('localhost', 8125)
24
+
25
+ # Increment a counter.
26
+ statsd.increment('page.views')
27
+
28
+ # Record a gauge 50% of the time.
29
+ statsd.gauge('users.online', 123, :sample_rate=>0.5)
30
+
31
+ # Sample a histogram
32
+ statsd.histogram('file.upload.size', 1234)
33
+
34
+ # Time a block of code
35
+ statsd.time('page.render') do
36
+ render_page('home.html')
37
+ end
38
+
39
+ # Send several metrics at the same time
40
+ # All metrics will be buffered and sent in one packet when the block completes
41
+ statsd.batch do |s|
42
+ s.increment('page.views')
43
+ s.gauge('users.online', 123)
44
+ end
45
+
46
+ # Tag a metric.
47
+ statsd.histogram('query.time', 10, :tags => ["version:1"])
48
+ ```
49
+
50
+ You can also post events to your stream. You can tag them, set priority and even aggregate them with other events.
51
+
52
+ Aggregation in the stream is made on hostname/event_type/source_type/aggregation_key.
53
+
54
+ ``` ruby
55
+ # Post a simple message
56
+ statsd.event("There might be a storm tomorrow", "A friend warned me earlier.")
57
+
58
+ # Cry for help
59
+ statsd.event("SO MUCH SNOW", "Started yesterday and it won't stop !!", :alert_type => "error", :tags => ["urgent", "endoftheworld"])
60
+ ```
61
+
62
+
63
+
64
+ Documentation
65
+ -------------
66
+
67
+ Full API documentation is available
68
+ [here](http://www.rubydoc.info/github/DataDog/dogstatsd-ruby/master/frames).
69
+
70
+
71
+ Feedback
72
+ --------
73
+
74
+ To suggest a feature, report a bug, or general discussion, head over
75
+ [here](http://github.com/DataDog/dogstatsd-ruby/issues/).
76
+
77
+
78
+ [Change Log](CHANGELOG.md)
79
+ ----------------------------
80
+
81
+
82
+ Credits
83
+ -------
84
+
85
+ dogstatsd-ruby is forked from Rien Henrichs [original Statsd
86
+ client](https://github.com/reinh/statsd).
87
+
88
+ Copyright (c) 2011 Rein Henrichs. See LICENSE.txt for
89
+ further details.
@@ -0,0 +1,377 @@
1
+ require 'socket'
2
+
3
+ # = Dogstatsd: A Dogstatsd client (https://www.datadoghq.com)
4
+ #
5
+ # @example Set up a global Dogstatsd client for a server on localhost:8125
6
+ # require 'dogstatsd'
7
+ # $statsd = Dogstatsd.new 'localhost', 8125
8
+ # @example Send some stats
9
+ # $statsd.increment 'page.views'
10
+ # $statsd.timing 'page.load', 320
11
+ # $statsd.gauge 'users.online', 100
12
+ # @example Use {#time} to time the execution of a block
13
+ # $statsd.time('account.activate') { @account.activate! }
14
+ # @example Create a namespaced statsd client and increment 'account.activate'
15
+ # statsd = Dogstatsd.new 'localhost', 8125, :namespace => 'account'
16
+ # statsd.increment 'activate'
17
+ # @example Create a statsd client with global tags
18
+ # statsd = Dogstatsd.new 'localhost', 8125, :tags => 'tag1:true'
19
+ class Dogstatsd
20
+
21
+ VERSION = '2.0.0'
22
+
23
+ DEFAULT_HOST = '127.0.0.1'
24
+ DEFAULT_PORT = 8125
25
+
26
+ # Create a dictionary to assign a key to every parameter's name, except for tags (treated differently)
27
+ # Goal: Simple and fast to add some other parameters
28
+ OPTS_KEYS = [
29
+ ['date_happened', 'd'],
30
+ ['hostname', 'h'],
31
+ ['aggregation_key', 'k'],
32
+ ['priority', 'p'],
33
+ ['source_type_name', 's'],
34
+ ['alert_type', 't']
35
+ ]
36
+
37
+ # Service check options
38
+ SC_OPT_KEYS = [
39
+ ['timestamp', 'd:'],
40
+ ['hostname', 'h:'],
41
+ ['tags', '#'],
42
+ ['message', 'm:']
43
+ ]
44
+ OK = 0
45
+ WARNING = 1
46
+ CRITICAL = 2
47
+ UNKNOWN = 3
48
+
49
+ # A namespace to prepend to all statsd calls. Defaults to no namespace.
50
+ attr_reader :namespace
51
+
52
+ # StatsD host. Defaults to 127.0.0.1.
53
+ attr_reader :host
54
+
55
+ # StatsD port. Defaults to 8125.
56
+ attr_reader :port
57
+
58
+ # Global tags to be added to every statsd call. Defaults to no tags.
59
+ attr_reader :tags
60
+
61
+ # Buffer containing the statsd message before they are sent in batch
62
+ attr_reader :buffer
63
+
64
+ # Maximum number of metrics in the buffer before it is flushed
65
+ attr_accessor :max_buffer_size
66
+
67
+ class << self
68
+ # Set to a standard logger instance to enable debug logging.
69
+ attr_accessor :logger
70
+ end
71
+
72
+ # @param [String] host your statsd host
73
+ # @param [Integer] port your statsd port
74
+ # @option opts [String] :namespace set a namespace to be prepended to every metric name
75
+ # @option opts [Array<String>] :tags tags to be added to every metric
76
+ def initialize(host = DEFAULT_HOST, port = DEFAULT_PORT, opts = {}, max_buffer_size=50)
77
+ self.host, self.port = host, port
78
+ @prefix = nil
79
+ @socket = UDPSocket.new
80
+ self.namespace = opts[:namespace]
81
+ self.tags = opts[:tags]
82
+ @buffer = Array.new
83
+ self.max_buffer_size = max_buffer_size
84
+ alias :send_stat :send_to_socket
85
+ end
86
+
87
+ def namespace=(namespace) #:nodoc:
88
+ @namespace = namespace
89
+ @prefix = namespace.nil? ? nil : "#{namespace}."
90
+ end
91
+
92
+ def host=(host) #:nodoc:
93
+ @host = host || '127.0.0.1'
94
+ end
95
+
96
+ def port=(port) #:nodoc:
97
+ @port = port || 8125
98
+ end
99
+
100
+ def tags=(tags) #:nodoc:
101
+ @tags = tags || []
102
+ end
103
+
104
+ # Sends an increment (count = 1) for the given stat to the statsd server.
105
+ #
106
+ # @param [String] stat stat name
107
+ # @param [Hash] opts the options to create the metric with
108
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
109
+ # @option opts [Array<String>] :tags An array of tags
110
+ # @see #count
111
+ def increment(stat, opts={})
112
+ count stat, 1, opts
113
+ end
114
+
115
+ # Sends a decrement (count = -1) for the given stat to the statsd server.
116
+ #
117
+ # @param [String] stat stat name
118
+ # @param [Hash] opts the options to create the metric with
119
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
120
+ # @option opts [Array<String>] :tags An array of tags
121
+ # @see #count
122
+ def decrement(stat, opts={})
123
+ count stat, -1, opts
124
+ end
125
+
126
+ # Sends an arbitrary count for the given stat to the statsd server.
127
+ #
128
+ # @param [String] stat stat name
129
+ # @param [Integer] count count
130
+ # @param [Hash] opts the options to create the metric with
131
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
132
+ # @option opts [Array<String>] :tags An array of tags
133
+ def count(stat, count, opts={})
134
+ send_stats stat, count, :c, opts
135
+ end
136
+
137
+ # Sends an arbitary gauge value for the given stat to the statsd server.
138
+ #
139
+ # This is useful for recording things like available disk space,
140
+ # memory usage, and the like, which have different semantics than
141
+ # counters.
142
+ #
143
+ # @param [String] stat stat name.
144
+ # @param [Numeric] value gauge value.
145
+ # @param [Hash] opts the options to create the metric with
146
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
147
+ # @option opts [Array<String>] :tags An array of tags
148
+ # @example Report the current user count:
149
+ # $statsd.gauge('user.count', User.count)
150
+ def gauge(stat, value, opts={})
151
+ send_stats stat, value, :g, opts
152
+ end
153
+
154
+ # Sends a value to be tracked as a histogram to the statsd server.
155
+ #
156
+ # @param [String] stat stat name.
157
+ # @param [Numeric] value histogram value.
158
+ # @param [Hash] opts the options to create the metric with
159
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
160
+ # @option opts [Array<String>] :tags An array of tags
161
+ # @example Report the current user count:
162
+ # $statsd.histogram('user.count', User.count)
163
+ def histogram(stat, value, opts={})
164
+ send_stats stat, value, :h, opts
165
+ end
166
+
167
+ # Sends a timing (in ms) for the given stat to the statsd server. The
168
+ # sample_rate determines what percentage of the time this report is sent. The
169
+ # statsd server then uses the sample_rate to correctly track the average
170
+ # timing for the stat.
171
+ #
172
+ # @param [String] stat stat name
173
+ # @param [Integer] ms timing in milliseconds
174
+ # @param [Hash] opts the options to create the metric with
175
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
176
+ # @option opts [Array<String>] :tags An array of tags
177
+ def timing(stat, ms, opts={})
178
+ send_stats stat, ms, :ms, opts
179
+ end
180
+
181
+ # Reports execution time of the provided block using {#timing}.
182
+ #
183
+ # If the block fails, the stat is still reported, then the error
184
+ # is reraised
185
+ #
186
+ # @param [String] stat stat name
187
+ # @param [Hash] opts the options to create the metric with
188
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
189
+ # @option opts [Array<String>] :tags An array of tags
190
+ # @yield The operation to be timed
191
+ # @see #timing
192
+ # @example Report the time (in ms) taken to activate an account
193
+ # $statsd.time('account.activate') { @account.activate! }
194
+ def time(stat, opts={})
195
+ start = Time.now
196
+ result = yield
197
+ time_since(stat, start, opts)
198
+ result
199
+ rescue
200
+ time_since(stat, start, opts)
201
+ raise
202
+ end
203
+ # Sends a value to be tracked as a set to the statsd server.
204
+ #
205
+ # @param [String] stat stat name.
206
+ # @param [Numeric] value set value.
207
+ # @param [Hash] opts the options to create the metric with
208
+ # @option opts [Numeric] :sample_rate sample rate, 1 for always
209
+ # @option opts [Array<String>] :tags An array of tags
210
+ # @example Record a unique visitory by id:
211
+ # $statsd.set('visitors.uniques', User.id)
212
+ def set(stat, value, opts={})
213
+ send_stats stat, value, :s, opts
214
+ end
215
+
216
+
217
+ # This method allows you to send custom service check statuses.
218
+ #
219
+ # @param [String] name Service check name
220
+ # @param [String] status Service check status.
221
+ # @param [Hash] opts the additional data about the service check
222
+ # @option opts [Integer, nil] :timestamp (nil) Assign a timestamp to the event. Default is now when none
223
+ # @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
224
+ # @option opts [Array<String>, nil] :tags (nil) An array of tags
225
+ # @option opts [String, nil] :message (nil) A message to associate with this service check status
226
+ # @example Report a critical service check status
227
+ # $statsd.service_check('my.service.check', Dogstatsd::CRITICAL, :tags=>['urgent'])
228
+ def service_check(name, status, opts={})
229
+ service_check_string = format_service_check(name, status, opts)
230
+ send_to_socket service_check_string
231
+ end
232
+ def format_service_check(name, status, opts={})
233
+ sc_string = "_sc|#{name}|#{status}"
234
+
235
+ SC_OPT_KEYS.each do |name_key|
236
+ if opts[name_key[0].to_sym]
237
+ if name_key[0] == 'tags'
238
+ tags = opts[:tags]
239
+ tags.each do |tag|
240
+ rm_pipes tag
241
+ end
242
+ tags = "#{tags.join(",")}" unless tags.empty?
243
+ sc_string << "|##{tags}"
244
+ elsif name_key[0] == 'message'
245
+ message = opts[:message]
246
+ rm_pipes message
247
+ escape_service_check_message message
248
+ sc_string << "|m:#{message}"
249
+ else
250
+ value = opts[name_key[0].to_sym]
251
+ rm_pipes value
252
+ sc_string << "|#{name_key[1]}#{value}"
253
+ end
254
+ end
255
+ end
256
+ return sc_string
257
+ end
258
+
259
+ # This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
260
+ #
261
+ # Aggregation in the stream is made on hostname/event_type/source_type/aggregation_key.
262
+ # If there's no event type, for example, then that won't matter;
263
+ # it will be grouped with other events that don't have an event type.
264
+ #
265
+ # @param [String] title Event title
266
+ # @param [String] text Event text. Supports \n
267
+ # @param [Hash] opts the additional data about the event
268
+ # @option opts [Integer, nil] :date_happened (nil) Assign a timestamp to the event. Default is now when none
269
+ # @option opts [String, nil] :hostname (nil) Assign a hostname to the event.
270
+ # @option opts [String, nil] :aggregation_key (nil) Assign an aggregation key to the event, to group it with some others
271
+ # @option opts [String, nil] :priority ('normal') Can be "normal" or "low"
272
+ # @option opts [String, nil] :source_type_name (nil) Assign a source type to the event
273
+ # @option opts [String, nil] :alert_type ('info') Can be "error", "warning", "info" or "success".
274
+ # @option opts [Array<String>] :tags tags to be added to every metric
275
+ # @example Report an awful event:
276
+ # $statsd.event('Something terrible happened', 'The end is near if we do nothing', :alert_type=>'warning', :tags=>['end_of_times','urgent'])
277
+ def event(title, text, opts={})
278
+ event_string = format_event(title, text, opts)
279
+ raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string.length > 8 * 1024
280
+
281
+ send_to_socket event_string
282
+ end
283
+
284
+ # Send several metrics in the same UDP Packet
285
+ # They will be buffered and flushed when the block finishes
286
+ #
287
+ # @example Send several metrics in one packet:
288
+ # $statsd.batch do |s|
289
+ # s.gauge('users.online',156)
290
+ # s.increment('page.views')
291
+ # end
292
+ def batch()
293
+ alias :send_stat :send_to_buffer
294
+ yield self
295
+ flush_buffer
296
+ alias :send_stat :send_to_socket
297
+ end
298
+
299
+ def format_event(title, text, opts={})
300
+ escape_event_content title
301
+ escape_event_content text
302
+ event_string_data = "_e{#{title.length},#{text.length}}:#{title}|#{text}"
303
+
304
+ # We construct the string to be sent by adding '|key:value' parts to it when needed
305
+ # All pipes ('|') in the metadata are removed. Title and Text can keep theirs
306
+ OPTS_KEYS.each do |name_key|
307
+ if name_key[0] != 'tags' && opts[name_key[0].to_sym]
308
+ value = opts[name_key[0].to_sym]
309
+ rm_pipes value
310
+ event_string_data << "|#{name_key[1]}:#{value}"
311
+ end
312
+ end
313
+ full_tags = tags + (opts[:tags] || [])
314
+ # Tags are joined and added as last part to the string to be sent
315
+ unless full_tags.empty?
316
+ full_tags.each do |tag|
317
+ rm_pipes tag
318
+ end
319
+ event_string_data << "|##{full_tags.join(',')}"
320
+ end
321
+
322
+ raise "Event #{title} payload is too big (more that 8KB), event discarded" if event_string_data.length > 8 * 1024
323
+ return event_string_data
324
+ end
325
+
326
+ private
327
+
328
+ def escape_event_content(msg)
329
+ msg.gsub! "\n", "\\n"
330
+ end
331
+
332
+ def rm_pipes(msg)
333
+ msg.gsub! "|", ""
334
+ end
335
+
336
+ def escape_service_check_message(msg)
337
+ msg.gsub! 'm:', 'm\:'
338
+ msg.gsub! "\n", "\\n"
339
+ end
340
+
341
+ def time_since(stat, start, opts)
342
+ timing(stat, ((Time.now - start) * 1000).round, opts)
343
+ end
344
+
345
+ def send_stats(stat, delta, type, opts={})
346
+ sample_rate = opts[:sample_rate] || 1
347
+ if sample_rate == 1 or rand < sample_rate
348
+ # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
349
+ stat = stat.to_s.gsub('::', '.').tr(':|@', '_')
350
+ rate = "|@#{sample_rate}" unless sample_rate == 1
351
+ ts = (tags || []) + (opts[:tags] || [])
352
+ tags = "|##{ts.join(",")}" unless ts.empty?
353
+ send_stat "#{@prefix}#{stat}:#{delta}|#{type}#{rate}#{tags}"
354
+ end
355
+ end
356
+
357
+ def send_to_buffer(message)
358
+ @buffer << message
359
+ if @buffer.length >= @max_buffer_size
360
+ flush_buffer
361
+ end
362
+ end
363
+
364
+ def flush_buffer()
365
+ send_to_socket(@buffer.join("\n"))
366
+ @buffer = Array.new
367
+ end
368
+
369
+ def send_to_socket(message)
370
+ self.class.logger.debug { "Dogstatsd: #{message}" } if self.class.logger
371
+ @socket.send(message, 0, @host, @port)
372
+ rescue => boom
373
+ self.class.logger.error { "Dogstatsd: #{boom.class} #{boom}" } if self.class.logger
374
+ nil
375
+ end
376
+ end
377
+
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dogstatsd
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Rein Henrichs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: yard
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.6.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.6.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: jeweler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A Ruby DogStastd client
70
+ email: code@datadoghq.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files:
74
+ - LICENSE.txt
75
+ - README.md
76
+ files:
77
+ - LICENSE.txt
78
+ - README.md
79
+ - lib/dogstatsd.rb
80
+ homepage: http://github.com/datadog/dogstatsd-ruby
81
+ licenses:
82
+ - MIT
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.4.5
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: A Ruby DogStatsd client
104
+ test_files: []
105
+ has_rdoc: