oboe 2.7.1.7-java → 2.7.2.2-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -0
- data/Gemfile +2 -0
- data/README.md +3 -3
- data/ext/oboe_metal/extconf.rb +11 -11
- data/lib/joboe_metal.rb +36 -42
- data/lib/oboe.rb +29 -27
- data/lib/oboe/api.rb +4 -0
- data/lib/oboe/api/layerinit.rb +9 -6
- data/lib/oboe/api/logging.rb +50 -28
- data/lib/oboe/api/memcache.rb +7 -5
- data/lib/oboe/api/profiling.rb +4 -4
- data/lib/oboe/api/tracing.rb +6 -5
- data/lib/oboe/api/util.rb +13 -9
- data/lib/oboe/base.rb +50 -15
- data/lib/oboe/config.rb +17 -15
- data/lib/oboe/frameworks/padrino.rb +0 -2
- data/lib/oboe/frameworks/padrino/templates.rb +5 -6
- data/lib/oboe/frameworks/rails.rb +0 -1
- data/lib/oboe/frameworks/rails/inst/action_controller.rb +4 -5
- data/lib/oboe/frameworks/rails/inst/action_view.rb +4 -4
- data/lib/oboe/frameworks/rails/inst/action_view_2x.rb +4 -4
- data/lib/oboe/frameworks/rails/inst/action_view_30.rb +2 -2
- data/lib/oboe/frameworks/rails/inst/active_record.rb +5 -5
- data/lib/oboe/frameworks/rails/inst/connection_adapters/mysql.rb +6 -6
- data/lib/oboe/frameworks/rails/inst/connection_adapters/mysql2.rb +3 -3
- data/lib/oboe/frameworks/rails/inst/connection_adapters/oracle.rb +1 -1
- data/lib/oboe/frameworks/rails/inst/connection_adapters/postgresql.rb +3 -3
- data/lib/oboe/frameworks/rails/inst/connection_adapters/utils.rb +18 -19
- data/lib/oboe/frameworks/sinatra.rb +4 -5
- data/lib/oboe/inst/cassandra.rb +17 -19
- data/lib/oboe/inst/dalli.rb +5 -5
- data/lib/oboe/inst/em-http-request.rb +13 -13
- data/lib/oboe/inst/faraday.rb +71 -0
- data/lib/oboe/inst/http.rb +4 -4
- data/lib/oboe/inst/memcache.rb +7 -10
- data/lib/oboe/inst/memcached.rb +7 -9
- data/lib/oboe/inst/mongo.rb +26 -28
- data/lib/oboe/inst/moped.rb +23 -24
- data/lib/oboe/inst/rack.rb +10 -11
- data/lib/oboe/inst/redis.rb +18 -20
- data/lib/oboe/inst/resque.rb +8 -9
- data/lib/oboe/instrumentation.rb +3 -0
- data/lib/oboe/loading.rb +19 -23
- data/lib/{method_profiling.rb → oboe/method_profiling.rb} +22 -8
- data/lib/oboe/ruby.rb +23 -3
- data/lib/oboe/thread_local.rb +9 -1
- data/lib/oboe/util.rb +15 -19
- data/lib/oboe/version.rb +5 -2
- data/lib/oboe/xtrace.rb +20 -24
- data/lib/oboe_metal.rb +16 -13
- data/lib/rails/generators/oboe/templates/oboe_initializer.rb +2 -0
- data/test/instrumentation/faraday_test.rb +142 -0
- data/test/instrumentation/moped_test.rb +2 -0
- data/test/minitest_helper.rb +0 -1
- data/test/support/config_test.rb +3 -1
- metadata +7 -3
data/lib/oboe/api/memcache.rb
CHANGED
@@ -3,22 +3,24 @@
|
|
3
3
|
|
4
4
|
module Oboe
|
5
5
|
module API
|
6
|
+
##
|
7
|
+
# Utility methods for the Memcache instrumentation
|
6
8
|
module Memcache
|
7
|
-
MEMCACHE_OPS = %w
|
9
|
+
MEMCACHE_OPS = %w(add append cas decr decrement delete fetch get incr increment prepend replace set)
|
8
10
|
|
9
11
|
def memcache_hit?(result)
|
10
12
|
result.nil? ? 0 : 1
|
11
13
|
end
|
12
14
|
|
13
15
|
def remote_host(key)
|
14
|
-
return unless defined?(Lib.memcached_server_by_key)\
|
15
|
-
|
16
|
+
return unless defined?(Lib.memcached_server_by_key) \
|
17
|
+
&& defined?(@struct) && defined?(is_unix_socket?)
|
16
18
|
|
17
|
-
server_as_array = Lib.memcached_server_by_key(@struct,
|
19
|
+
server_as_array = Lib.memcached_server_by_key(@struct, key.to_s)
|
18
20
|
if server_as_array.is_a?(Array)
|
19
21
|
server = server_as_array.first
|
20
22
|
if is_unix_socket?(server)
|
21
|
-
return
|
23
|
+
return 'localhost'
|
22
24
|
elsif defined?(server.hostname)
|
23
25
|
return server.hostname
|
24
26
|
end
|
data/lib/oboe/api/profiling.rb
CHANGED
@@ -3,8 +3,9 @@
|
|
3
3
|
|
4
4
|
module Oboe
|
5
5
|
module API
|
6
|
+
##
|
7
|
+
# Module that provides profiling of arbitrary blocks of code
|
6
8
|
module Profiling
|
7
|
-
|
8
9
|
##
|
9
10
|
# Public: Profile a given block of code. Detect any exceptions thrown by
|
10
11
|
# the block and report errors.
|
@@ -24,8 +25,7 @@ module Oboe
|
|
24
25
|
# end
|
25
26
|
#
|
26
27
|
# Returns the result of the block.
|
27
|
-
def profile(profile_name, report_kvs={}, with_backtrace=false)
|
28
|
-
|
28
|
+
def profile(profile_name, report_kvs = {}, with_backtrace = false)
|
29
29
|
report_kvs[:Language] ||= :ruby
|
30
30
|
report_kvs[:ProfileName] ||= profile_name
|
31
31
|
report_kvs[:Backtrace] = Oboe::API.backtrace if with_backtrace
|
@@ -34,7 +34,7 @@ module Oboe
|
|
34
34
|
|
35
35
|
begin
|
36
36
|
yield
|
37
|
-
rescue
|
37
|
+
rescue => e
|
38
38
|
log_exception(nil, e)
|
39
39
|
raise
|
40
40
|
ensure
|
data/lib/oboe/api/tracing.rb
CHANGED
@@ -3,8 +3,9 @@
|
|
3
3
|
|
4
4
|
module Oboe
|
5
5
|
module API
|
6
|
+
##
|
7
|
+
# Provides the higher-level tracing interface for the API.
|
6
8
|
module Tracing
|
7
|
-
|
8
9
|
# Public: Trace a given block of code. Detect any exceptions thrown by
|
9
10
|
# the block and report errors.
|
10
11
|
#
|
@@ -30,7 +31,7 @@ module Oboe
|
|
30
31
|
# result = computation_with_oboe(1000)
|
31
32
|
#
|
32
33
|
# Returns the result of the block.
|
33
|
-
def trace(layer, opts={}, protect_op=nil)
|
34
|
+
def trace(layer, opts = {}, protect_op = nil)
|
34
35
|
log_entry(layer, opts, protect_op)
|
35
36
|
begin
|
36
37
|
yield
|
@@ -73,7 +74,7 @@ module Oboe
|
|
73
74
|
# Returns a list of length two, the first element of which is the result
|
74
75
|
# of the block, and the second element of which is the oboe context that
|
75
76
|
# was set when the block completed execution.
|
76
|
-
def start_trace(layer, xtrace=nil, opts={})
|
77
|
+
def start_trace(layer, xtrace = nil, opts = {})
|
77
78
|
log_start(layer, xtrace, opts)
|
78
79
|
begin
|
79
80
|
result = yield
|
@@ -114,7 +115,7 @@ module Oboe
|
|
114
115
|
# end
|
115
116
|
#
|
116
117
|
# Returns the result of the block.
|
117
|
-
def start_trace_with_target(layer, xtrace, target, opts={})
|
118
|
+
def start_trace_with_target(layer, xtrace, target, opts = {})
|
118
119
|
log_start(layer, xtrace, opts)
|
119
120
|
exit_evt = Oboe::Context.createEvent
|
120
121
|
begin
|
@@ -124,7 +125,7 @@ module Oboe
|
|
124
125
|
log_exception(layer, e)
|
125
126
|
raise
|
126
127
|
ensure
|
127
|
-
exit_evt.addEdge(Oboe::Context.get
|
128
|
+
exit_evt.addEdge(Oboe::Context.get)
|
128
129
|
log_event(layer, 'exit', exit_evt)
|
129
130
|
Oboe::Context.clear
|
130
131
|
end
|
data/lib/oboe/api/util.rb
CHANGED
@@ -5,6 +5,8 @@ require 'pp'
|
|
5
5
|
|
6
6
|
module Oboe
|
7
7
|
module API
|
8
|
+
##
|
9
|
+
# General utility methods for the gem
|
8
10
|
module Util
|
9
11
|
BACKTRACE_CUTOFF = 200
|
10
12
|
|
@@ -15,7 +17,7 @@ module Oboe
|
|
15
17
|
#
|
16
18
|
# Return a boolean indicating whether or not key is reserved.
|
17
19
|
def valid_key?(key)
|
18
|
-
!%w
|
20
|
+
!%w(Label Layer Edge Timestamp Timestamp_u).include? key.to_s
|
19
21
|
end
|
20
22
|
|
21
23
|
# Internal: Get the current backtrace.
|
@@ -25,7 +27,9 @@ module Oboe
|
|
25
27
|
# made.
|
26
28
|
#
|
27
29
|
# Returns a string with each frame of the backtrace separated by '\r\n'.
|
28
|
-
|
30
|
+
#
|
31
|
+
# FIXME: ignore is not currently used (see BACKTRACE_CUTOFF)
|
32
|
+
def backtrace(_ignore = 1)
|
29
33
|
trim_backtrace(Kernel.caller).join("\r\n")
|
30
34
|
end
|
31
35
|
|
@@ -59,7 +63,7 @@ module Oboe
|
|
59
63
|
# Ensure that the blacklist is an array
|
60
64
|
unless Oboe::Config.blacklist.is_a?(Array)
|
61
65
|
val = Oboe::Config[:blacklist]
|
62
|
-
Oboe::Config[:blacklist] = [
|
66
|
+
Oboe::Config[:blacklist] = [val.to_s]
|
63
67
|
end
|
64
68
|
|
65
69
|
Oboe::Config.blacklist.each do |h|
|
@@ -93,25 +97,25 @@ module Oboe
|
|
93
97
|
# Returns a string representation of klass
|
94
98
|
def get_class_name(klass)
|
95
99
|
kv = {}
|
100
|
+
|
96
101
|
if klass.to_s =~ /::/
|
97
102
|
klass.class.to_s.rpartition('::').last
|
98
103
|
else
|
99
|
-
if klass.is_a?(Class)
|
104
|
+
if klass.is_a?(Class) && klass.is_a?(Module)
|
100
105
|
# Class
|
101
|
-
kv[
|
106
|
+
kv['Class'] = klass.to_s
|
102
107
|
|
103
|
-
elsif (
|
108
|
+
elsif (!klass.is_a?(Class) && !klass.is_a?(Module))
|
104
109
|
# Class instance
|
105
|
-
kv[
|
110
|
+
kv['Class'] = klass.class.to_s
|
106
111
|
|
107
112
|
else
|
108
113
|
# Module
|
109
|
-
kv[
|
114
|
+
kv['Module'] = klass.to_s
|
110
115
|
end
|
111
116
|
end
|
112
117
|
kv
|
113
118
|
end
|
114
|
-
|
115
119
|
end
|
116
120
|
end
|
117
121
|
end
|
data/lib/oboe/base.rb
CHANGED
@@ -22,6 +22,10 @@ SAMPLE_SOURCE_MASK = 0b1111000000000000000000000000
|
|
22
22
|
ZERO_SAMPLE_RATE_MASK = 0b1111000000000000000000000000
|
23
23
|
ZERO_SAMPLE_SOURCE_MASK = 0b0000111111111111111111111111
|
24
24
|
|
25
|
+
##
|
26
|
+
# This module is the base module for the various implementations of Oboe reporting.
|
27
|
+
# Current variations as of 2014-09-10 are a c-extension, JRuby (using TraceView Java
|
28
|
+
# instrumentation) and a Heroku c-extension (with embedded tracelyzer)
|
25
29
|
module OboeBase
|
26
30
|
extend ::Oboe::ThreadLocal
|
27
31
|
|
@@ -31,7 +35,7 @@ module OboeBase
|
|
31
35
|
attr_accessor :sample_rate
|
32
36
|
thread_local :layer_op
|
33
37
|
|
34
|
-
def self.included(
|
38
|
+
def self.included(_)
|
35
39
|
self.loaded = true
|
36
40
|
end
|
37
41
|
|
@@ -43,26 +47,46 @@ module OboeBase
|
|
43
47
|
end
|
44
48
|
end
|
45
49
|
|
50
|
+
##
|
51
|
+
# Returns true if the tracing_mode is set to always.
|
52
|
+
# False otherwise
|
53
|
+
#
|
46
54
|
def always?
|
47
|
-
Oboe::Config[:tracing_mode].to_s ==
|
55
|
+
Oboe::Config[:tracing_mode].to_s == 'always'
|
48
56
|
end
|
49
57
|
|
58
|
+
##
|
59
|
+
# Returns true if the tracing_mode is set to never.
|
60
|
+
# False otherwise
|
61
|
+
#
|
50
62
|
def never?
|
51
|
-
Oboe::Config[:tracing_mode].to_s ==
|
63
|
+
Oboe::Config[:tracing_mode].to_s == 'never'
|
52
64
|
end
|
53
65
|
|
66
|
+
##
|
67
|
+
# Returns true if the tracing_mode is set to always or through.
|
68
|
+
# False otherwise
|
69
|
+
#
|
54
70
|
def passthrough?
|
55
|
-
|
71
|
+
%w(always through).include?(Oboe::Config[:tracing_mode])
|
56
72
|
end
|
57
73
|
|
74
|
+
##
|
75
|
+
# Returns true if the tracing_mode is set to through.
|
76
|
+
# False otherwise
|
77
|
+
#
|
58
78
|
def through?
|
59
|
-
Oboe::Config[:tracing_mode] ==
|
79
|
+
Oboe::Config[:tracing_mode] == 'through'
|
60
80
|
end
|
61
81
|
|
82
|
+
##
|
83
|
+
# Returns true if we are currently tracing a request
|
84
|
+
# False otherwise
|
85
|
+
#
|
62
86
|
def tracing?
|
63
87
|
return false unless Oboe.loaded
|
64
88
|
|
65
|
-
Oboe::Context.isValid
|
89
|
+
Oboe::Context.isValid && !Oboe.never?
|
66
90
|
end
|
67
91
|
|
68
92
|
def log(layer, label, options = {})
|
@@ -74,28 +98,39 @@ module OboeBase
|
|
74
98
|
false
|
75
99
|
end
|
76
100
|
|
101
|
+
##
|
102
|
+
# Determines if we are running under a forking webserver
|
103
|
+
#
|
77
104
|
def forking_webserver?
|
78
|
-
(defined?(::Unicorn)
|
105
|
+
(defined?(::Unicorn) && ($PROGRAM_NAME =~ /unicorn/i)) ? true : false
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Indicates whether a supported framework is in use
|
110
|
+
# or not
|
111
|
+
#
|
112
|
+
def framework?
|
113
|
+
defined?(::Rails) or defined?(::Sinatra) or defined?(::Padrino) or defined?(::Grape)
|
79
114
|
end
|
80
115
|
|
81
116
|
##
|
82
117
|
# These methods should be implemented by the descendants
|
83
118
|
# (Oboe_metal, Oboe_metal (JRuby), Heroku_metal)
|
84
119
|
#
|
85
|
-
def sample?(
|
86
|
-
|
120
|
+
def sample?(_opts = {})
|
121
|
+
fail 'sample? should be implemented by metal layer.'
|
87
122
|
end
|
88
123
|
|
89
|
-
def log(
|
90
|
-
|
124
|
+
def log(_layer, _label, _options = {})
|
125
|
+
fail 'log should be implemented by metal layer.'
|
91
126
|
end
|
92
127
|
|
93
|
-
def set_tracing_mode(
|
94
|
-
|
128
|
+
def set_tracing_mode(_mode)
|
129
|
+
fail 'set_tracing_mode should be implemented by metal layer.'
|
95
130
|
end
|
96
131
|
|
97
|
-
def set_sample_rate(
|
98
|
-
|
132
|
+
def set_sample_rate(_rate)
|
133
|
+
fail 'set_sample_rate should be implemented by metal layer.'
|
99
134
|
end
|
100
135
|
end
|
101
136
|
|
data/lib/oboe/config.rb
CHANGED
@@ -11,9 +11,9 @@ module Oboe
|
|
11
11
|
module Config
|
12
12
|
@@config = {}
|
13
13
|
|
14
|
-
@@instrumentation = [
|
15
|
-
|
16
|
-
|
14
|
+
@@instrumentation = [:action_controller, :action_view, :active_record,
|
15
|
+
:cassandra, :dalli, :em_http_request, :faraday, :nethttp, :memcached,
|
16
|
+
:memcache, :mongo, :moped, :rack, :redis, :resque]
|
17
17
|
##
|
18
18
|
# Return the raw nested hash.
|
19
19
|
#
|
@@ -21,7 +21,7 @@ module Oboe
|
|
21
21
|
@@config
|
22
22
|
end
|
23
23
|
|
24
|
-
def self.initialize(
|
24
|
+
def self.initialize(_data = {})
|
25
25
|
# Setup default instrumentation values
|
26
26
|
@@instrumentation.each do |k|
|
27
27
|
@@config[k] = {}
|
@@ -39,6 +39,7 @@ module Oboe
|
|
39
39
|
Oboe::Config[:action_view][:collect_backtraces] = true
|
40
40
|
Oboe::Config[:cassandra][:collect_backtraces] = true
|
41
41
|
Oboe::Config[:dalli][:collect_backtraces] = false
|
42
|
+
Oboe::Config[:faraday][:collect_backtraces] = false
|
42
43
|
Oboe::Config[:em_http_request][:collect_backtraces] = false
|
43
44
|
Oboe::Config[:memcache][:collect_backtraces] = false
|
44
45
|
Oboe::Config[:memcached][:collect_backtraces] = false
|
@@ -61,23 +62,23 @@ module Oboe
|
|
61
62
|
@@config[:blacklist] = []
|
62
63
|
|
63
64
|
# Access Key is empty until loaded from config file or env var
|
64
|
-
@@config[:access_key] =
|
65
|
+
@@config[:access_key] = ''
|
65
66
|
|
66
67
|
# The oboe Ruby client has the ability to sanitize query literals
|
67
68
|
# from SQL statements. By default this is disabled. Enable to
|
68
69
|
# avoid collecting and reporting query literals to TraceView.
|
69
70
|
@@config[:sanitize_sql] = false
|
70
71
|
|
71
|
-
if ENV.
|
72
|
+
if ENV.key?('OPENSHIFT_TRACEVIEW_TLYZER_IP')
|
72
73
|
# We're running on OpenShift
|
73
|
-
@@config[:tracing_mode] =
|
74
|
+
@@config[:tracing_mode] = 'always'
|
74
75
|
@@config[:reporter_host] = ENV['OPENSHIFT_TRACEVIEW_TLYZER_IP']
|
75
76
|
@@config[:reporter_port] = ENV['OPENSHIFT_TRACEVIEW_TLYZER_PORT']
|
76
77
|
else
|
77
78
|
# The default configuration
|
78
|
-
@@config[:tracing_mode] =
|
79
|
-
@@config[:reporter_host] =
|
80
|
-
@@config[:reporter_port] =
|
79
|
+
@@config[:tracing_mode] = 'through'
|
80
|
+
@@config[:reporter_host] = '127.0.0.1'
|
81
|
+
@@config[:reporter_port] = '7831'
|
81
82
|
end
|
82
83
|
|
83
84
|
@@config[:verbose] = false
|
@@ -101,17 +102,18 @@ module Oboe
|
|
101
102
|
@@config[key.to_sym] = value
|
102
103
|
|
103
104
|
if key == :sampling_rate
|
104
|
-
Oboe.logger.warn
|
105
|
+
Oboe.logger.warn 'sampling_rate is not a supported setting for Oboe::Config. ' \
|
106
|
+
'Please use :sample_rate.'
|
105
107
|
end
|
106
108
|
|
107
109
|
if key == :sample_rate
|
108
|
-
unless value.is_a?(Integer)
|
109
|
-
|
110
|
+
unless value.is_a?(Integer) || value.is_a?(Float)
|
111
|
+
fail 'oboe :sample_rate must be a number between 1 and 1000000 (1m)'
|
110
112
|
end
|
111
113
|
|
112
114
|
# Validate :sample_rate value
|
113
115
|
unless value.between?(1, 1e6)
|
114
|
-
|
116
|
+
fail 'oboe :sample_rate must be between 1 and 1000000 (1m)'
|
115
117
|
end
|
116
118
|
|
117
119
|
# Assure value is an integer
|
@@ -134,7 +136,7 @@ module Oboe
|
|
134
136
|
if sym.to_s =~ /(.+)=$/
|
135
137
|
self[$1] = args.first
|
136
138
|
else
|
137
|
-
unless @@config.
|
139
|
+
unless @@config.key?(sym)
|
138
140
|
Oboe.logger.warn "[oboe/warn] Unknown method call on Oboe::Config: #{sym}"
|
139
141
|
end
|
140
142
|
self[sym]
|
@@ -8,10 +8,8 @@ module Oboe
|
|
8
8
|
::Oboe::Util.method_alias(klass, :render, ::Padrino::Rendering)
|
9
9
|
end
|
10
10
|
|
11
|
-
def render_with_oboe(engine, data=nil, options={}, locals={}, &block)
|
12
|
-
|
13
|
-
render_without_oboe(engine, data, options, locals, &block)
|
14
|
-
else
|
11
|
+
def render_with_oboe(engine, data = nil, options = {}, locals = {}, &block)
|
12
|
+
if Oboe.tracing?
|
15
13
|
report_kvs = {}
|
16
14
|
|
17
15
|
if data
|
@@ -32,7 +30,7 @@ module Oboe
|
|
32
30
|
report_kvs[:LineNumber] = __LINE__
|
33
31
|
rescue StandardError => e
|
34
32
|
::Oboe.logger.debug e.message
|
35
|
-
::Oboe.logger.debug e.backtrace.join(
|
33
|
+
::Oboe.logger.debug e.backtrace.join(', ')
|
36
34
|
end
|
37
35
|
|
38
36
|
Oboe::API.profile(report_kvs[:template], report_kvs, false) do
|
@@ -51,9 +49,10 @@ module Oboe
|
|
51
49
|
::Oboe::API.log_exit('render', report_kvs)
|
52
50
|
end
|
53
51
|
end
|
52
|
+
else
|
53
|
+
render_without_oboe(engine, data, options, locals, &block)
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
59
|
-
|
@@ -69,7 +69,7 @@ module Oboe
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
-
if defined?(ActionController::Base)
|
72
|
+
if defined?(ActionController::Base) && Oboe::Config[:action_controller][:enabled]
|
73
73
|
if ::Rails::VERSION::MAJOR == 4
|
74
74
|
|
75
75
|
class ActionController::Base
|
@@ -98,8 +98,8 @@ if defined?(ActionController::Base) and Oboe::Config[:action_controller][:enable
|
|
98
98
|
|
99
99
|
def perform_action(*arguments)
|
100
100
|
report_kvs = {
|
101
|
-
|
102
|
-
|
101
|
+
:Controller => @_request.path_parameters['controller'],
|
102
|
+
:Action => @_request.path_parameters['action']
|
103
103
|
}
|
104
104
|
Oboe::API.log(nil, 'info', report_kvs)
|
105
105
|
perform_action_without_oboe(*arguments)
|
@@ -117,7 +117,6 @@ if defined?(ActionController::Base) and Oboe::Config[:action_controller][:enable
|
|
117
117
|
end
|
118
118
|
end
|
119
119
|
end
|
120
|
-
Oboe.logger.info
|
120
|
+
Oboe.logger.info '[oboe/loading] Instrumenting actioncontroler' if Oboe::Config[:verbose]
|
121
121
|
end
|
122
122
|
# vim:set expandtab:tabstop=2
|
123
|
-
|