oneapm_rpm 1.1.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.
Files changed (234) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +30 -0
  3. data/.rubocop.yml +725 -0
  4. data/Gemfile +3 -0
  5. data/Guardfile +7 -0
  6. data/LICENSE +1 -0
  7. data/README.md +3 -0
  8. data/config/cert/cacert.pem +1177 -0
  9. data/config/database.yml +5 -0
  10. data/lib/initializers/goliath.rb +11 -0
  11. data/lib/initializers/other.rb +1 -0
  12. data/lib/initializers/rails.rb +15 -0
  13. data/lib/one_apm/agent.rb +253 -0
  14. data/lib/one_apm/agent/agent.rb +283 -0
  15. data/lib/one_apm/agent/agent/connect.rb +175 -0
  16. data/lib/one_apm/agent/agent/container_data_manager.rb +218 -0
  17. data/lib/one_apm/agent/agent/forkable_dispatcher_functions.rb +96 -0
  18. data/lib/one_apm/agent/agent/helpers.rb +45 -0
  19. data/lib/one_apm/agent/agent/start.rb +226 -0
  20. data/lib/one_apm/agent/agent/start_worker_thread.rb +148 -0
  21. data/lib/one_apm/agent/busy_calculator.rb +115 -0
  22. data/lib/one_apm/agent/cross_app/cross_app_monitor.rb +181 -0
  23. data/lib/one_apm/agent/cross_app/cross_app_tracing.rb +336 -0
  24. data/lib/one_apm/agent/database.rb +308 -0
  25. data/lib/one_apm/agent/database/active_record_helper.rb +80 -0
  26. data/lib/one_apm/agent/database/obfuscation_helpers.rb +76 -0
  27. data/lib/one_apm/agent/database/obfuscator.rb +78 -0
  28. data/lib/one_apm/agent/database/postgres_explain_obfuscator.rb +45 -0
  29. data/lib/one_apm/agent/datastores.rb +175 -0
  30. data/lib/one_apm/agent/datastores/metric_helper.rb +83 -0
  31. data/lib/one_apm/agent/datastores/mongo.rb +27 -0
  32. data/lib/one_apm/agent/datastores/mongo/metric_translator.rb +189 -0
  33. data/lib/one_apm/agent/datastores/mongo/obfuscator.rb +37 -0
  34. data/lib/one_apm/agent/datastores/mongo/statement_formatter.rb +51 -0
  35. data/lib/one_apm/agent/event/event_listener.rb +40 -0
  36. data/lib/one_apm/agent/event/event_loop.rb +191 -0
  37. data/lib/one_apm/agent/event/worker_loop.rb +97 -0
  38. data/lib/one_apm/agent/harvester.rb +48 -0
  39. data/lib/one_apm/agent/inbound_request_monitor.rb +30 -0
  40. data/lib/one_apm/agent/javascript_instrumentor.rb +186 -0
  41. data/lib/one_apm/agent/pipe/pipe_channel_manager.rb +275 -0
  42. data/lib/one_apm/agent/pipe/pipe_service.rb +81 -0
  43. data/lib/one_apm/agent/sampler.rb +55 -0
  44. data/lib/one_apm/agent/sampler_collection.rb +65 -0
  45. data/lib/one_apm/agent/samplers/cpu_sampler.rb +49 -0
  46. data/lib/one_apm/agent/samplers/delayed_job_sampler.rb +109 -0
  47. data/lib/one_apm/agent/samplers/memory_sampler.rb +144 -0
  48. data/lib/one_apm/agent/samplers/object_sampler.rb +22 -0
  49. data/lib/one_apm/agent/samplers/vm_sampler.rb +124 -0
  50. data/lib/one_apm/agent/synthetics_monitor.rb +48 -0
  51. data/lib/one_apm/agent/threading/agent_thread.rb +74 -0
  52. data/lib/one_apm/agent/threading/backtrace_node.rb +133 -0
  53. data/lib/one_apm/agent/threading/backtrace_service.rb +259 -0
  54. data/lib/one_apm/agent/threading/thread_profile.rb +155 -0
  55. data/lib/one_apm/collector/collector/helper.rb +139 -0
  56. data/lib/one_apm/collector/collector/http_connection.rb +254 -0
  57. data/lib/one_apm/collector/collector/server_methods.rb +71 -0
  58. data/lib/one_apm/collector/collector_service.rb +123 -0
  59. data/lib/one_apm/collector/commands/agent_command.rb +17 -0
  60. data/lib/one_apm/collector/commands/thread_profiler_session.rb +108 -0
  61. data/lib/one_apm/collector/commands/xray_session.rb +53 -0
  62. data/lib/one_apm/collector/commands/xray_session_collection.rb +156 -0
  63. data/lib/one_apm/collector/containers/agent_command_router.rb +153 -0
  64. data/lib/one_apm/collector/containers/custom_event_aggregator.rb +94 -0
  65. data/lib/one_apm/collector/containers/error_collector.rb +349 -0
  66. data/lib/one_apm/collector/containers/sql_sampler.rb +331 -0
  67. data/lib/one_apm/collector/containers/stats_engine.rb +34 -0
  68. data/lib/one_apm/collector/containers/transaction_event_aggregator.rb +249 -0
  69. data/lib/one_apm/collector/containers/transaction_sampler.rb +352 -0
  70. data/lib/one_apm/collector/containers/utilization_data.rb +36 -0
  71. data/lib/one_apm/collector/stats_engine/gc_profiler.rb +106 -0
  72. data/lib/one_apm/collector/stats_engine/metric_stats.rb +243 -0
  73. data/lib/one_apm/collector/stats_engine/stats_hash.rb +105 -0
  74. data/lib/one_apm/configuration.rb +429 -0
  75. data/lib/one_apm/configuration/autostart.rb +41 -0
  76. data/lib/one_apm/configuration/default_source.rb +1026 -0
  77. data/lib/one_apm/configuration/environment_source.rb +113 -0
  78. data/lib/one_apm/configuration/high_security_source.rb +56 -0
  79. data/lib/one_apm/configuration/manual_source.rb +13 -0
  80. data/lib/one_apm/configuration/server_source.rb +60 -0
  81. data/lib/one_apm/configuration/yaml_source.rb +134 -0
  82. data/lib/one_apm/errors/agent_errors.rb +26 -0
  83. data/lib/one_apm/errors/internal_agent_error.rb +16 -0
  84. data/lib/one_apm/errors/noticed_error.rb +79 -0
  85. data/lib/one_apm/frameworks/external.rb +15 -0
  86. data/lib/one_apm/frameworks/rails.rb +103 -0
  87. data/lib/one_apm/frameworks/rails3.rb +37 -0
  88. data/lib/one_apm/frameworks/rails4.rb +21 -0
  89. data/lib/one_apm/frameworks/ruby.rb +21 -0
  90. data/lib/one_apm/frameworks/sinatra.rb +12 -0
  91. data/lib/one_apm/inst/3rd/active_merchant.rb +35 -0
  92. data/lib/one_apm/inst/3rd/acts_as_solr.rb +70 -0
  93. data/lib/one_apm/inst/3rd/authlogic.rb +23 -0
  94. data/lib/one_apm/inst/3rd/sunspot.rb +31 -0
  95. data/lib/one_apm/inst/background_job/active_job.rb +88 -0
  96. data/lib/one_apm/inst/background_job/delayed_job.rb +52 -0
  97. data/lib/one_apm/inst/background_job/delayed_job_injection.rb +8 -0
  98. data/lib/one_apm/inst/background_job/resque.rb +107 -0
  99. data/lib/one_apm/inst/background_job/sidekiq.rb +64 -0
  100. data/lib/one_apm/inst/dispatcher/passenger.rb +25 -0
  101. data/lib/one_apm/inst/dispatcher/rainbows.rb +23 -0
  102. data/lib/one_apm/inst/framework/grape.rb +94 -0
  103. data/lib/one_apm/inst/framework/padrino.rb +30 -0
  104. data/lib/one_apm/inst/framework/sinatra.rb +185 -0
  105. data/lib/one_apm/inst/framework/sinatra/ignorer.rb +50 -0
  106. data/lib/one_apm/inst/framework/sinatra/transaction_namer.rb +54 -0
  107. data/lib/one_apm/inst/http_clients/curb.rb +189 -0
  108. data/lib/one_apm/inst/http_clients/excon.rb +70 -0
  109. data/lib/one_apm/inst/http_clients/excon/connection.rb +31 -0
  110. data/lib/one_apm/inst/http_clients/excon/middleware.rb +55 -0
  111. data/lib/one_apm/inst/http_clients/httpclient.rb +44 -0
  112. data/lib/one_apm/inst/http_clients/net.rb +34 -0
  113. data/lib/one_apm/inst/http_clients/typhoeus.rb +76 -0
  114. data/lib/one_apm/inst/nosql/memcache.rb +134 -0
  115. data/lib/one_apm/inst/nosql/mongo.rb +126 -0
  116. data/lib/one_apm/inst/nosql/mongo_moped.rb +85 -0
  117. data/lib/one_apm/inst/nosql/redis.rb +83 -0
  118. data/lib/one_apm/inst/orm/active_record.rb +99 -0
  119. data/lib/one_apm/inst/orm/active_record_4.rb +28 -0
  120. data/lib/one_apm/inst/orm/data_mapper.rb +180 -0
  121. data/lib/one_apm/inst/orm/sequel.rb +47 -0
  122. data/lib/one_apm/inst/rack.rb +38 -0
  123. data/lib/one_apm/inst/rack/rack.rb +44 -0
  124. data/lib/one_apm/inst/rack/rack_builder.rb +51 -0
  125. data/lib/one_apm/inst/rails/action_controller.rb +118 -0
  126. data/lib/one_apm/inst/rails/action_web_service.rb +44 -0
  127. data/lib/one_apm/inst/rails/errors.rb +43 -0
  128. data/lib/one_apm/inst/rails3/action_controller.rb +172 -0
  129. data/lib/one_apm/inst/rails3/errors.rb +43 -0
  130. data/lib/one_apm/inst/rails4/action_controller.rb +27 -0
  131. data/lib/one_apm/inst/rails4/action_controller_subscriber.rb +121 -0
  132. data/lib/one_apm/inst/rails4/action_view.rb +23 -0
  133. data/lib/one_apm/inst/rails4/action_view_subscriber.rb +93 -0
  134. data/lib/one_apm/inst/rails4/active_record_subscriber.rb +96 -0
  135. data/lib/one_apm/inst/rails4/errors.rb +42 -0
  136. data/lib/one_apm/inst/rails_middleware.rb +40 -0
  137. data/lib/one_apm/inst/support/evented_subscriber.rb +98 -0
  138. data/lib/one_apm/inst/support/ignore_actions.rb +39 -0
  139. data/lib/one_apm/inst/support/queue_time.rb +76 -0
  140. data/lib/one_apm/inst/transaction_base.rb +405 -0
  141. data/lib/one_apm/logger/agent_logger.rb +206 -0
  142. data/lib/one_apm/logger/audit_logger.rb +78 -0
  143. data/lib/one_apm/logger/memory_logger.rb +50 -0
  144. data/lib/one_apm/logger/null_logger.rb +19 -0
  145. data/lib/one_apm/metrics/metric_data.rb +72 -0
  146. data/lib/one_apm/metrics/metric_spec.rb +82 -0
  147. data/lib/one_apm/metrics/stats.rb +173 -0
  148. data/lib/one_apm/probe.rb +16 -0
  149. data/lib/one_apm/probe/framework_loader.rb +53 -0
  150. data/lib/one_apm/probe/instance_methods.rb +105 -0
  151. data/lib/one_apm/probe/instrumentation.rb +60 -0
  152. data/lib/one_apm/rack/browser_monitoring.rb +144 -0
  153. data/lib/one_apm/rack/middleware_base.rb +27 -0
  154. data/lib/one_apm/rack/middleware_hooks.rb +17 -0
  155. data/lib/one_apm/rack/middleware_tracing.rb +81 -0
  156. data/lib/one_apm/rack/middleware_wrapper.rb +86 -0
  157. data/lib/one_apm/support/chained_call.rb +15 -0
  158. data/lib/one_apm/support/coerce.rb +81 -0
  159. data/lib/one_apm/support/collection_helper.rb +79 -0
  160. data/lib/one_apm/support/dotted_hash.rb +45 -0
  161. data/lib/one_apm/support/encoders.rb +34 -0
  162. data/lib/one_apm/support/environment_report.rb +127 -0
  163. data/lib/one_apm/support/event_buffer.rb +82 -0
  164. data/lib/one_apm/support/event_buffer/sampled_buffer.rb +45 -0
  165. data/lib/one_apm/support/event_buffer/sized_buffer.rb +21 -0
  166. data/lib/one_apm/support/event_buffer/synthetics_event_buffer.rb +40 -0
  167. data/lib/one_apm/support/helper.rb +49 -0
  168. data/lib/one_apm/support/hostname.rb +13 -0
  169. data/lib/one_apm/support/http_clients/curb_wrappers.rb +65 -0
  170. data/lib/one_apm/support/http_clients/excon_wrappers.rb +63 -0
  171. data/lib/one_apm/support/http_clients/httpclient_wrappers.rb +61 -0
  172. data/lib/one_apm/support/http_clients/net_http_wrappers.rb +48 -0
  173. data/lib/one_apm/support/http_clients/typhoeus_wrappers.rb +73 -0
  174. data/lib/one_apm/support/http_clients/uri_util.rb +39 -0
  175. data/lib/one_apm/support/json_marshaller.rb +68 -0
  176. data/lib/one_apm/support/json_wrapper.rb +130 -0
  177. data/lib/one_apm/support/language_support.rb +142 -0
  178. data/lib/one_apm/support/library_detection.rb +119 -0
  179. data/lib/one_apm/support/local_environment.rb +196 -0
  180. data/lib/one_apm/support/marshaller.rb +62 -0
  181. data/lib/one_apm/support/method_tracer.rb +334 -0
  182. data/lib/one_apm/support/method_tracer/helpers.rb +92 -0
  183. data/lib/one_apm/support/method_tracer/traced_method_stack.rb +103 -0
  184. data/lib/one_apm/support/obfuscator.rb +47 -0
  185. data/lib/one_apm/support/okjson.rb +601 -0
  186. data/lib/one_apm/support/parameter_filtering.rb +35 -0
  187. data/lib/one_apm/support/rules_engine.rb +56 -0
  188. data/lib/one_apm/support/rules_engine/replacement_rule.rb +80 -0
  189. data/lib/one_apm/support/rules_engine/segment_terms_rule.rb +46 -0
  190. data/lib/one_apm/support/server.rb +11 -0
  191. data/lib/one_apm/support/supported_versions.rb +257 -0
  192. data/lib/one_apm/support/system_info.rb +211 -0
  193. data/lib/one_apm/support/timer_lib.rb +29 -0
  194. data/lib/one_apm/support/version_number.rb +51 -0
  195. data/lib/one_apm/support/vm.rb +30 -0
  196. data/lib/one_apm/support/vm/jruby_vm.rb +38 -0
  197. data/lib/one_apm/support/vm/monotonic_gc_profiler.rb +43 -0
  198. data/lib/one_apm/support/vm/mri_vm.rb +85 -0
  199. data/lib/one_apm/support/vm/rubinius_vm.rb +129 -0
  200. data/lib/one_apm/support/vm/snapshot.rb +18 -0
  201. data/lib/one_apm/transaction.rb +336 -0
  202. data/lib/one_apm/transaction/class_methods.rb +132 -0
  203. data/lib/one_apm/transaction/instance_helpers.rb +82 -0
  204. data/lib/one_apm/transaction/metric_constants.rb +42 -0
  205. data/lib/one_apm/transaction/sample_buffer/force_persist_sample_buffer.rb +21 -0
  206. data/lib/one_apm/transaction/sample_buffer/slowest_sample_buffer.rb +21 -0
  207. data/lib/one_apm/transaction/sample_buffer/synthetics_sample_buffer.rb +21 -0
  208. data/lib/one_apm/transaction/sample_buffer/transaction_sample_buffer.rb +101 -0
  209. data/lib/one_apm/transaction/sample_buffer/xray_sample_buffer.rb +60 -0
  210. data/lib/one_apm/transaction/segment.rb +193 -0
  211. data/lib/one_apm/transaction/segment_summary.rb +51 -0
  212. data/lib/one_apm/transaction/thread_local_access.rb +73 -0
  213. data/lib/one_apm/transaction/transaction_analysis.rb +78 -0
  214. data/lib/one_apm/transaction/transaction_apdex.rb +20 -0
  215. data/lib/one_apm/transaction/transaction_cpu.rb +22 -0
  216. data/lib/one_apm/transaction/transaction_finish_append.rb +67 -0
  217. data/lib/one_apm/transaction/transaction_ignore.rb +33 -0
  218. data/lib/one_apm/transaction/transaction_jruby_functions.rb +40 -0
  219. data/lib/one_apm/transaction/transaction_metrics.rb +53 -0
  220. data/lib/one_apm/transaction/transaction_name.rb +90 -0
  221. data/lib/one_apm/transaction/transaction_namer.rb +49 -0
  222. data/lib/one_apm/transaction/transaction_sample.rb +204 -0
  223. data/lib/one_apm/transaction/transaction_sample_builder.rb +168 -0
  224. data/lib/one_apm/transaction/transaction_state.rb +149 -0
  225. data/lib/one_apm/transaction/transaction_summary.rb +28 -0
  226. data/lib/one_apm/transaction/transaction_synthetics.rb +40 -0
  227. data/lib/one_apm/transaction/transaction_timings.rb +54 -0
  228. data/lib/one_apm/version.rb +13 -0
  229. data/lib/oneapm_rpm.rb +16 -0
  230. data/lib/sequel/extensions/oneapm_instrumentation.rb +84 -0
  231. data/lib/sequel/plugins/oneapm_instrumentation.rb +66 -0
  232. data/oneapm.yml +135 -0
  233. data/oneapm_rpm.gemspec +58 -0
  234. metadata +474 -0
@@ -0,0 +1,103 @@
1
+ # encoding: utf-8
2
+
3
+ module OneApm
4
+ module Support
5
+
6
+ class TracedMethodFrame
7
+
8
+ attr_reader :tag
9
+ attr_accessor :name, :start_time, :children_time
10
+
11
+ def initialize(tag, start_time)
12
+ @tag = tag
13
+ @start_time = start_time
14
+ @children_time = 0
15
+ end
16
+
17
+ end
18
+
19
+ # TracedMethodStack is responsible for tracking the push and pop of methods
20
+ # that we are tracing, notifying the transaction sampler, and calculating
21
+ # exclusive time when a method is complete. This is allowed whether a
22
+ # transaction is in progress not.
23
+ class TracedMethodStack
24
+
25
+ def initialize
26
+ @stack = []
27
+ end
28
+
29
+ # Pushes a frame onto the transaction stack - this generates a
30
+ # TransactionSample::Segment at the end of transaction execution.
31
+ #
32
+ # The generated segment won't be named until pop_frame is called.
33
+ #
34
+ # +tag+ should be a Symbol, and is only for debugging purposes to
35
+ # identify this frame if the stack gets corrupted.
36
+ def push_frame(state, tag, time = Time.now.to_f)
37
+ transaction_sampler.notice_push_frame(state, time) if sampler_enabled?
38
+ frame = TracedMethodFrame.new(tag, time)
39
+ @stack.push frame
40
+ frame
41
+ end
42
+
43
+ # Pops a frame off the transaction stack - this updates the transaction
44
+ # sampler that we've finished execution of a traced method.
45
+ #
46
+ # +expected_frame+ should be TracedMethodFrame from the corresponding
47
+ # push_frame call.
48
+ #
49
+ # +name+ will be applied to the generated transaction trace segment.
50
+ def pop_frame(state, expected_frame, name, time, deduct_call_time_from_parent=true)
51
+ frame = fetch_matching_frame(expected_frame)
52
+
53
+ note_children_time(frame, time, deduct_call_time_from_parent)
54
+
55
+ transaction_sampler.notice_pop_frame(state, name, time) if sampler_enabled?
56
+ frame.name = name
57
+ frame
58
+ end
59
+
60
+ def fetch_matching_frame(expected_frame)
61
+ while frame = @stack.pop
62
+ if frame == expected_frame
63
+ return frame
64
+ else
65
+ OneApm::Agent.logger.info("Unexpected frame in traced method stack: #{frame.inspect} expected to be #{expected_frame.inspect}")
66
+ OneApm::Agent.logger.debug do
67
+ ["Backtrace for unexpected frame: ", caller.join("\n")]
68
+ end
69
+ end
70
+ end
71
+
72
+ raise "Frame not found in blame stack: #{expected_frame.inspect}"
73
+ end
74
+
75
+ def note_children_time(frame, time, deduct_call_time_from_parent)
76
+ if !@stack.empty?
77
+ if deduct_call_time_from_parent
78
+ @stack.last.children_time += (time - frame.start_time)
79
+ else
80
+ @stack.last.children_time += frame.children_time
81
+ end
82
+ end
83
+ end
84
+
85
+ def sampler_enabled?
86
+ Agent.config[:'transaction_tracer.enabled']
87
+ end
88
+
89
+ def transaction_sampler
90
+ Agent.instance.transaction_sampler
91
+ end
92
+
93
+ def clear
94
+ @stack.clear
95
+ end
96
+
97
+ def empty?
98
+ @stack.empty?
99
+ end
100
+
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+
3
+ #require 'base64'
4
+
5
+ module OneApm
6
+ module Agent
7
+ class Obfuscator
8
+
9
+ attr_reader :key_bytes
10
+
11
+ EMPTY_KEY_BYTES = [0]
12
+ PACK_FORMAT = 'm'
13
+
14
+ # RUM uses a shortened key, so just trim it up front
15
+ def initialize(key, length=nil)
16
+ if key.nil? || key.empty?
17
+ @key_bytes = EMPTY_KEY_BYTES
18
+ else
19
+ @key_bytes = key.bytes.to_a
20
+ @key_bytes = @key_bytes.first(length) if length
21
+ end
22
+ end
23
+
24
+ def obfuscate(text)
25
+ [ encode(text) ].pack(PACK_FORMAT).gsub(/\n/, '')
26
+ end
27
+
28
+ def deobfuscate(text)
29
+ encode(text.unpack(PACK_FORMAT).first )
30
+ end
31
+
32
+ def encode(text)
33
+ return text unless key_bytes
34
+
35
+ encoded = ""
36
+ encoded.force_encoding('binary') if encoded.respond_to?( :force_encoding )
37
+ index = 0
38
+ text.each_byte do |byte|
39
+ encoded.concat((byte ^ key_bytes[index % key_bytes.length]))
40
+ index+=1
41
+ end
42
+ encoded
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,601 @@
1
+ # encoding: utf-8
2
+
3
+ #
4
+ # Copyright 2011, 2012 Keith Rarick
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+
24
+
25
+ require 'stringio'
26
+
27
+ # Some parts adapted from
28
+ # http://golang.org/src/pkg/json/decode.go and
29
+ # http://golang.org/src/pkg/utf8/utf8.go
30
+ module OneApm
31
+ module Support
32
+ module OkJson
33
+ Upstream = 'LTD7LBKLZWFF7OZK'
34
+ extend self
35
+
36
+
37
+ # Decodes a json document in string s and
38
+ # returns the corresponding ruby value.
39
+ # String s must be valid UTF-8. If you have
40
+ # a string in some other encoding, convert
41
+ # it first.
42
+ #
43
+ # String values in the resulting structure
44
+ # will be UTF-8.
45
+ def decode(s)
46
+ ts = lex(s)
47
+ v, ts = textparse(ts)
48
+ if ts.length > 0
49
+ raise Error, 'trailing garbage'
50
+ end
51
+ v
52
+ end
53
+
54
+
55
+ # Parses a "json text" in the sense of RFC 4627.
56
+ # Returns the parsed value and any trailing tokens.
57
+ # Note: this is almost the same as valparse,
58
+ # except that it does not accept atomic values.
59
+ def textparse(ts)
60
+ if ts.length < 0
61
+ raise Error, 'empty'
62
+ end
63
+
64
+ typ, _, val = ts[0]
65
+ case typ
66
+ when '{' then objparse(ts)
67
+ when '[' then arrparse(ts)
68
+ else
69
+ raise Error, "unexpected #{val.inspect}"
70
+ end
71
+ end
72
+
73
+
74
+ # Parses a "value" in the sense of RFC 4627.
75
+ # Returns the parsed value and any trailing tokens.
76
+ def valparse(ts)
77
+ if ts.length < 0
78
+ raise Error, 'empty'
79
+ end
80
+
81
+ typ, _, val = ts[0]
82
+ case typ
83
+ when '{' then objparse(ts)
84
+ when '[' then arrparse(ts)
85
+ when :val,:str then [val, ts[1..-1]]
86
+ else
87
+ raise Error, "unexpected #{val.inspect}"
88
+ end
89
+ end
90
+
91
+
92
+ # Parses an "object" in the sense of RFC 4627.
93
+ # Returns the parsed value and any trailing tokens.
94
+ def objparse(ts)
95
+ ts = eat('{', ts)
96
+ obj = {}
97
+
98
+ if ts[0][0] == '}'
99
+ return obj, ts[1..-1]
100
+ end
101
+
102
+ k, v, ts = pairparse(ts)
103
+ obj[k] = v
104
+
105
+ if ts[0][0] == '}'
106
+ return obj, ts[1..-1]
107
+ end
108
+
109
+ loop do
110
+ ts = eat(',', ts)
111
+
112
+ k, v, ts = pairparse(ts)
113
+ obj[k] = v
114
+
115
+ if ts[0][0] == '}'
116
+ return obj, ts[1..-1]
117
+ end
118
+ end
119
+ end
120
+
121
+
122
+ # Parses a "member" in the sense of RFC 4627.
123
+ # Returns the parsed values and any trailing tokens.
124
+ def pairparse(ts)
125
+ (typ, _, k), ts = ts[0], ts[1..-1]
126
+ if typ != :str
127
+ raise Error, "unexpected #{k.inspect}"
128
+ end
129
+ ts = eat(':', ts)
130
+ v, ts = valparse(ts)
131
+ [k, v, ts]
132
+ end
133
+
134
+
135
+ # Parses an "array" in the sense of RFC 4627.
136
+ # Returns the parsed value and any trailing tokens.
137
+ def arrparse(ts)
138
+ ts = eat('[', ts)
139
+ arr = []
140
+
141
+ if ts[0][0] == ']'
142
+ return arr, ts[1..-1]
143
+ end
144
+
145
+ v, ts = valparse(ts)
146
+ arr << v
147
+
148
+ if ts[0][0] == ']'
149
+ return arr, ts[1..-1]
150
+ end
151
+
152
+ loop do
153
+ ts = eat(',', ts)
154
+
155
+ v, ts = valparse(ts)
156
+ arr << v
157
+
158
+ if ts[0][0] == ']'
159
+ return arr, ts[1..-1]
160
+ end
161
+ end
162
+ end
163
+
164
+
165
+ def eat(typ, ts)
166
+ if ts[0][0] != typ
167
+ raise Error, "expected #{typ} (got #{ts[0].inspect})"
168
+ end
169
+ ts[1..-1]
170
+ end
171
+
172
+
173
+ # Scans s and returns a list of json tokens,
174
+ # excluding white space (as defined in RFC 4627).
175
+ def lex(s)
176
+ ts = []
177
+ while s.length > 0
178
+ typ, lexeme, val = tok(s)
179
+ if typ == nil
180
+ raise Error, "invalid character at #{s[0,10].inspect}"
181
+ end
182
+ if typ != :space
183
+ ts << [typ, lexeme, val]
184
+ end
185
+ s = s[lexeme.length..-1]
186
+ end
187
+ ts
188
+ end
189
+
190
+
191
+ # Scans the first token in s and
192
+ # returns a 3-element list, or nil
193
+ # if s does not begin with a valid token.
194
+ #
195
+ # The first list element is one of
196
+ # '{', '}', ':', ',', '[', ']',
197
+ # :val, :str, and :space.
198
+ #
199
+ # The second element is the lexeme.
200
+ #
201
+ # The third element is the value of the
202
+ # token for :val and :str, otherwise
203
+ # it is the lexeme.
204
+ def tok(s)
205
+ case s[0]
206
+ when ?{ then ['{', s[0,1], s[0,1]]
207
+ when ?} then ['}', s[0,1], s[0,1]]
208
+ when ?: then [':', s[0,1], s[0,1]]
209
+ when ?, then [',', s[0,1], s[0,1]]
210
+ when ?[ then ['[', s[0,1], s[0,1]]
211
+ when ?] then [']', s[0,1], s[0,1]]
212
+ when ?n then nulltok(s)
213
+ when ?t then truetok(s)
214
+ when ?f then falsetok(s)
215
+ when ?" then strtok(s)
216
+ when Spc then [:space, s[0,1], s[0,1]]
217
+ when ?\t then [:space, s[0,1], s[0,1]]
218
+ when ?\n then [:space, s[0,1], s[0,1]]
219
+ when ?\r then [:space, s[0,1], s[0,1]]
220
+ else numtok(s)
221
+ end
222
+ end
223
+
224
+
225
+ def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end
226
+ def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end
227
+ def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end
228
+
229
+
230
+ def numtok(s)
231
+ m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
232
+ if m && m.begin(0) == 0
233
+ if m[3] && !m[2]
234
+ [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
235
+ elsif m[2]
236
+ [:val, m[0], Float(m[0])]
237
+ else
238
+ [:val, m[0], Integer(m[0])]
239
+ end
240
+ else
241
+ []
242
+ end
243
+ end
244
+
245
+
246
+ def strtok(s)
247
+ m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
248
+ if ! m
249
+ raise Error, "invalid string literal at #{abbrev(s)}"
250
+ end
251
+ [:str, m[0], unquote(m[0])]
252
+ end
253
+
254
+
255
+ def abbrev(s)
256
+ t = s[0,10]
257
+ p = t['`']
258
+ t = t[0,p] if p
259
+ t = t + '...' if t.length < s.length
260
+ '`' + t + '`'
261
+ end
262
+
263
+
264
+ # Converts a quoted json string literal q into a UTF-8-encoded string.
265
+ # The rules are different than for Ruby, so we cannot use eval.
266
+ # Unquote will raise an error if q contains control characters.
267
+ def unquote(q)
268
+ q = q[1...-1]
269
+ a = q.dup # allocate a big enough string
270
+ rubydoesenc = false
271
+ # In ruby >= 1.9, a[w] is a codepoint, not a byte.
272
+ if a.class.method_defined?(:force_encoding)
273
+ a.force_encoding('UTF-8')
274
+ rubydoesenc = true
275
+ end
276
+ r, w = 0, 0
277
+ while r < q.length
278
+ c = q[r]
279
+ case true
280
+ when c == ?\\
281
+ r += 1
282
+ if r >= q.length
283
+ raise Error, "string literal ends with a \"\\\": \"#{q}\""
284
+ end
285
+
286
+ case q[r]
287
+ when ?",?\\,?/,?'
288
+ a[w] = q[r]
289
+ r += 1
290
+ w += 1
291
+ when ?b,?f,?n,?r,?t
292
+ a[w] = Unesc[q[r]]
293
+ r += 1
294
+ w += 1
295
+ when ?u
296
+ r += 1
297
+ uchar = begin
298
+ hexdec4(q[r,4])
299
+ rescue RuntimeError => e
300
+ raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
301
+ end
302
+ r += 4
303
+ if surrogate? uchar
304
+ if q.length >= r+6
305
+ uchar1 = hexdec4(q[r+2,4])
306
+ uchar = subst(uchar, uchar1)
307
+ if uchar != Ucharerr
308
+ # A valid pair; consume.
309
+ r += 6
310
+ end
311
+ end
312
+ end
313
+ if rubydoesenc
314
+ a[w] = '' << uchar
315
+ w += 1
316
+ else
317
+ w += ucharenc(a, w, uchar)
318
+ end
319
+ else
320
+ raise Error, "invalid escape char #{q[r]} in \"#{q}\""
321
+ end
322
+ when c == ?", c < Spc
323
+ raise Error, "invalid character in string literal \"#{q}\""
324
+ else
325
+ # Copy anything else byte-for-byte.
326
+ # Valid UTF-8 will remain valid UTF-8.
327
+ # Invalid UTF-8 will remain invalid UTF-8.
328
+ # In ruby >= 1.9, c is a codepoint, not a byte,
329
+ # in which case this is still what we want.
330
+ a[w] = c
331
+ r += 1
332
+ w += 1
333
+ end
334
+ end
335
+ a[0,w]
336
+ end
337
+
338
+
339
+ # Encodes unicode character u as UTF-8
340
+ # bytes in string a at position i.
341
+ # Returns the number of bytes written.
342
+ def ucharenc(a, i, u)
343
+ case true
344
+ when u <= Uchar1max
345
+ a[i] = (u & 0xff).chr
346
+ 1
347
+ when u <= Uchar2max
348
+ a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
349
+ a[i+1] = (Utagx | (u&Umaskx)).chr
350
+ 2
351
+ when u <= Uchar3max
352
+ a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
353
+ a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
354
+ a[i+2] = (Utagx | (u&Umaskx)).chr
355
+ 3
356
+ else
357
+ a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
358
+ a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
359
+ a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
360
+ a[i+3] = (Utagx | (u&Umaskx)).chr
361
+ 4
362
+ end
363
+ end
364
+
365
+
366
+ def hexdec4(s)
367
+ if s.length != 4
368
+ raise Error, 'short'
369
+ end
370
+ (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
371
+ end
372
+
373
+
374
+ def subst(u1, u2)
375
+ if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
376
+ return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
377
+ end
378
+ return Ucharerr
379
+ end
380
+
381
+
382
+ def surrogate?(u)
383
+ Usurr1 <= u && u < Usurr3
384
+ end
385
+
386
+
387
+ def nibble(c)
388
+ case true
389
+ when ?0 <= c && c <= ?9 then c.ord - ?0.ord
390
+ when ?a <= c && c <= ?z then c.ord - ?a.ord + 10
391
+ when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
392
+ else
393
+ raise Error, "invalid hex code #{c}"
394
+ end
395
+ end
396
+
397
+
398
+ # Encodes x into a json text. It may contain only
399
+ # Array, Hash, String, Numeric, true, false, nil.
400
+ # (Note, this list excludes Symbol.)
401
+ # X itself must be an Array or a Hash.
402
+ # No other value can be encoded, and an error will
403
+ # be raised if x contains any other value, such as
404
+ # Nan, Infinity, Symbol, and Proc, or if a Hash key
405
+ # is not a String.
406
+ # Strings contained in x must be valid UTF-8.
407
+ def encode(x)
408
+ case x
409
+ when Hash then objenc(x)
410
+ when Array then arrenc(x)
411
+ else
412
+ raise Error, 'root value must be an Array or a Hash'
413
+ end
414
+ end
415
+
416
+
417
+ def valenc(x)
418
+ case x
419
+ when Hash then objenc(x)
420
+ when Array then arrenc(x)
421
+ when String then strenc(x)
422
+ when Numeric then numenc(x)
423
+ when true then "true"
424
+ when false then "false"
425
+ when nil then "null"
426
+ else
427
+ raise Error, "cannot encode #{x.class}: #{x.inspect}"
428
+ end
429
+ end
430
+
431
+
432
+ def objenc(x)
433
+ '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
434
+ end
435
+
436
+
437
+ def arrenc(a)
438
+ '[' + a.map{|x| valenc(x)}.join(',') + ']'
439
+ end
440
+
441
+
442
+ def keyenc(k)
443
+ case k
444
+ when String then strenc(k)
445
+ else
446
+ raise Error, "Hash key is not a string: #{k.inspect}"
447
+ end
448
+ end
449
+
450
+
451
+ def strenc(s)
452
+ t = StringIO.new
453
+ t.putc(?")
454
+ r = 0
455
+
456
+ # In ruby >= 1.9, s[r] is a codepoint, not a byte.
457
+ rubydoesenc = s.class.method_defined?(:encoding)
458
+
459
+ while r < s.length
460
+ case s[r]
461
+ when ?" then t.print('\\"')
462
+ when ?\\ then t.print('\\\\')
463
+ when ?\b then t.print('\\b')
464
+ when ?\f then t.print('\\f')
465
+ when ?\n then t.print('\\n')
466
+ when ?\r then t.print('\\r')
467
+ when ?\t then t.print('\\t')
468
+ else
469
+ c = s[r]
470
+ case true
471
+ when rubydoesenc
472
+ begin
473
+ c.ord # will raise an error if c is invalid UTF-8
474
+ t.write(c)
475
+ rescue
476
+ t.write(Ustrerr)
477
+ end
478
+ when Spc <= c && c <= ?~
479
+ t.putc(c)
480
+ else
481
+ n = ucharcopy(t, s, r) # ensure valid UTF-8 output
482
+ r += n - 1 # r is incremented below
483
+ end
484
+ end
485
+ r += 1
486
+ end
487
+ t.putc(?")
488
+ t.string
489
+ end
490
+
491
+
492
+ def numenc(x)
493
+ if ((x.nan? || x.infinite?) rescue false)
494
+ raise Error, "Numeric cannot be represented: #{x}"
495
+ end
496
+ "#{x}"
497
+ end
498
+
499
+
500
+ # Copies the valid UTF-8 bytes of a single character
501
+ # from string s at position i to I/O object t, and
502
+ # returns the number of bytes copied.
503
+ # If no valid UTF-8 char exists at position i,
504
+ # ucharcopy writes Ustrerr and returns 1.
505
+ def ucharcopy(t, s, i)
506
+ n = s.length - i
507
+ raise Utf8Error if n < 1
508
+
509
+ c0 = s[i].ord
510
+
511
+ # 1-byte, 7-bit sequence?
512
+ if c0 < Utagx
513
+ t.putc(c0)
514
+ return 1
515
+ end
516
+
517
+ raise Utf8Error if c0 < Utag2 # unexpected continuation byte?
518
+
519
+ raise Utf8Error if n < 2 # need continuation byte
520
+ c1 = s[i+1].ord
521
+ raise Utf8Error if c1 < Utagx || Utag2 <= c1
522
+
523
+ # 2-byte, 11-bit sequence?
524
+ if c0 < Utag3
525
+ raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max
526
+ t.putc(c0)
527
+ t.putc(c1)
528
+ return 2
529
+ end
530
+
531
+ # need second continuation byte
532
+ raise Utf8Error if n < 3
533
+
534
+ c2 = s[i+2].ord
535
+ raise Utf8Error if c2 < Utagx || Utag2 <= c2
536
+
537
+ # 3-byte, 16-bit sequence?
538
+ if c0 < Utag4
539
+ u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
540
+ raise Utf8Error if u <= Uchar2max
541
+ t.putc(c0)
542
+ t.putc(c1)
543
+ t.putc(c2)
544
+ return 3
545
+ end
546
+
547
+ # need third continuation byte
548
+ raise Utf8Error if n < 4
549
+ c3 = s[i+3].ord
550
+ raise Utf8Error if c3 < Utagx || Utag2 <= c3
551
+
552
+ # 4-byte, 21-bit sequence?
553
+ if c0 < Utag5
554
+ u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
555
+ raise Utf8Error if u <= Uchar3max
556
+ t.putc(c0)
557
+ t.putc(c1)
558
+ t.putc(c2)
559
+ t.putc(c3)
560
+ return 4
561
+ end
562
+
563
+ raise Utf8Error
564
+ rescue Utf8Error
565
+ t.write(Ustrerr)
566
+ return 1
567
+ end
568
+
569
+
570
+ class Utf8Error < ::StandardError
571
+ end
572
+
573
+
574
+ class Error < ::StandardError
575
+ end
576
+
577
+
578
+ Utagx = 0x80 # 1000 0000
579
+ Utag2 = 0xc0 # 1100 0000
580
+ Utag3 = 0xe0 # 1110 0000
581
+ Utag4 = 0xf0 # 1111 0000
582
+ Utag5 = 0xF8 # 1111 1000
583
+ Umaskx = 0x3f # 0011 1111
584
+ Umask2 = 0x1f # 0001 1111
585
+ Umask3 = 0x0f # 0000 1111
586
+ Umask4 = 0x07 # 0000 0111
587
+ Uchar1max = (1<<7) - 1
588
+ Uchar2max = (1<<11) - 1
589
+ Uchar3max = (1<<16) - 1
590
+ Ucharerr = 0xFFFD # unicode "replacement char"
591
+ Ustrerr = "\xef\xbf\xbd" # unicode "replacement char"
592
+ Usurrself = 0x10000
593
+ Usurr1 = 0xd800
594
+ Usurr2 = 0xdc00
595
+ Usurr3 = 0xe000
596
+
597
+ Spc = ' '[0]
598
+ Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t}
599
+ end
600
+ end
601
+ end