drone 1.0.1 → 1.0.2
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.
- metadata +3 -41
- data/.gitignore +0 -8
- data/.rvmrc +0 -1
- data/.yardopts +0 -1
- data/Gemfile +0 -4
- data/LICENSE +0 -20
- data/README.md +0 -162
- data/Rakefile +0 -20
- data/drone.gemspec +0 -29
- data/examples/simple.rb +0 -50
- data/lib/drone.rb +0 -23
- data/lib/drone/core.rb +0 -141
- data/lib/drone/interfaces/base.rb +0 -17
- data/lib/drone/interfaces/console.rb +0 -82
- data/lib/drone/metrics/counter.rb +0 -40
- data/lib/drone/metrics/gauge.rb +0 -25
- data/lib/drone/metrics/histogram.rb +0 -153
- data/lib/drone/metrics/meter.rb +0 -82
- data/lib/drone/metrics/metric.rb +0 -16
- data/lib/drone/metrics/timer.rb +0 -57
- data/lib/drone/monitoring.rb +0 -107
- data/lib/drone/schedulers/eventmachine.rb +0 -70
- data/lib/drone/storage/base.rb +0 -122
- data/lib/drone/storage/memory.rb +0 -58
- data/lib/drone/utils/ewma.rb +0 -55
- data/lib/drone/utils/exponentially_decaying_sample.rb +0 -81
- data/lib/drone/utils/uniform_sample.rb +0 -52
- data/lib/drone/version.rb +0 -3
- data/specs/all.rb +0 -11
- data/specs/common.rb +0 -63
- data/specs/metrics/counter_spec.rb +0 -43
- data/specs/metrics/gauge_spec.rb +0 -28
- data/specs/metrics/meter_spec.rb +0 -61
- data/specs/metrics/timer_spec.rb +0 -111
- data/specs/schedulers/eventmachine_spec.rb +0 -76
- data/specs/unit/ewma_spec.rb +0 -141
- data/specs/unit/exponentially_decaying_sample_spec.rb +0 -86
- data/specs/unit/histogram_spec.rb +0 -91
- data/specs/unit/monitoring_spec.rb +0 -129
- data/specs/unit/uniform_sample_spec.rb +0 -46
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: drone
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 1.0.
|
5
|
+
version: 1.0.2
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Julien Ammous
|
@@ -87,46 +87,8 @@ extensions: []
|
|
87
87
|
|
88
88
|
extra_rdoc_files: []
|
89
89
|
|
90
|
-
files:
|
91
|
-
|
92
|
-
- .rvmrc
|
93
|
-
- .yardopts
|
94
|
-
- Gemfile
|
95
|
-
- LICENSE
|
96
|
-
- README.md
|
97
|
-
- Rakefile
|
98
|
-
- drone.gemspec
|
99
|
-
- examples/simple.rb
|
100
|
-
- lib/drone.rb
|
101
|
-
- lib/drone/core.rb
|
102
|
-
- lib/drone/interfaces/base.rb
|
103
|
-
- lib/drone/interfaces/console.rb
|
104
|
-
- lib/drone/metrics/counter.rb
|
105
|
-
- lib/drone/metrics/gauge.rb
|
106
|
-
- lib/drone/metrics/histogram.rb
|
107
|
-
- lib/drone/metrics/meter.rb
|
108
|
-
- lib/drone/metrics/metric.rb
|
109
|
-
- lib/drone/metrics/timer.rb
|
110
|
-
- lib/drone/monitoring.rb
|
111
|
-
- lib/drone/schedulers/eventmachine.rb
|
112
|
-
- lib/drone/storage/base.rb
|
113
|
-
- lib/drone/storage/memory.rb
|
114
|
-
- lib/drone/utils/ewma.rb
|
115
|
-
- lib/drone/utils/exponentially_decaying_sample.rb
|
116
|
-
- lib/drone/utils/uniform_sample.rb
|
117
|
-
- lib/drone/version.rb
|
118
|
-
- specs/all.rb
|
119
|
-
- specs/common.rb
|
120
|
-
- specs/metrics/counter_spec.rb
|
121
|
-
- specs/metrics/gauge_spec.rb
|
122
|
-
- specs/metrics/meter_spec.rb
|
123
|
-
- specs/metrics/timer_spec.rb
|
124
|
-
- specs/schedulers/eventmachine_spec.rb
|
125
|
-
- specs/unit/ewma_spec.rb
|
126
|
-
- specs/unit/exponentially_decaying_sample_spec.rb
|
127
|
-
- specs/unit/histogram_spec.rb
|
128
|
-
- specs/unit/monitoring_spec.rb
|
129
|
-
- specs/unit/uniform_sample_spec.rb
|
90
|
+
files: []
|
91
|
+
|
130
92
|
homepage: ""
|
131
93
|
licenses: []
|
132
94
|
|
data/.gitignore
DELETED
data/.rvmrc
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
rvm use 1.9.2
|
data/.yardopts
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
--no-private lib/**/*.rb - README.md LICENSE
|
data/Gemfile
DELETED
data/LICENSE
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
Copyright (c) 2011-2011 Julien Ammous
|
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.
|
data/README.md
DELETED
@@ -1,162 +0,0 @@
|
|
1
|
-
|
2
|
-
# What is this ?
|
3
|
-
|
4
|
-
Drone is a monitoring library designed to collect data from your application and export them
|
5
|
-
to virtually any monitoring tool.
|
6
|
-
Its core is heavily based on the impressive works of Coda Hale on the metrics java library.
|
7
|
-
|
8
|
-
A fully working example is included in examples/simple, to run it
|
9
|
-
(I suppose you already cloned the repository and have a prompt at the root of it):
|
10
|
-
|
11
|
-
gem install bundler
|
12
|
-
bundle
|
13
|
-
ruby examples/simple.rb
|
14
|
-
|
15
|
-
The example will output the collected statistics directly on the console every second.
|
16
|
-
|
17
|
-
# Supported Runtimes
|
18
|
-
|
19
|
-
- MRI 1.8.7+
|
20
|
-
- Rubinius 1.2.2+
|
21
|
-
|
22
|
-
|
23
|
-
# Status
|
24
|
-
- Most of the features I wanted in are (see below for usage examples):
|
25
|
-
- timing method calls
|
26
|
-
- method calls rate
|
27
|
-
- counters
|
28
|
-
- gauges
|
29
|
-
|
30
|
-
- Good test coverage
|
31
|
-
|
32
|
-
The gem was created for a specific need and is currently used in preproduction environment,
|
33
|
-
no major bugs until now.
|
34
|
-
|
35
|
-
|
36
|
-
# How is it done
|
37
|
-
|
38
|
-
The library is split in different parts
|
39
|
-
|
40
|
-
- the core:<br/>
|
41
|
-
it contains all the API used to declare which data to collect and how as well as the storage for them
|
42
|
-
|
43
|
-
- the metrics:<br/>
|
44
|
-
that is all the metrics type the library know.
|
45
|
-
|
46
|
-
- the interfaces:<br/>
|
47
|
-
those are the parts which will decides how the stored data are made available.
|
48
|
-
|
49
|
-
- the schedulers:</br>
|
50
|
-
this is where the timers are scheduled, currently there is only one scheduler: eventmachine
|
51
|
-
|
52
|
-
- the storage:<br/>
|
53
|
-
this part decides where the actual data for the metrics are stored, the default is to store them
|
54
|
-
in memory but other possible options are: redis, memcached, etc...
|
55
|
-
The goal for external storage is to allow concurrent applications to share the same metrics, an
|
56
|
-
immediate example of such application is a rails application ran under passenger or any other spawner
|
57
|
-
|
58
|
-
## Constraints
|
59
|
-
|
60
|
-
- the name of each metric can be formatted how it pleases you (note that output interfaces may expect some format)
|
61
|
-
but the name is expected to be unique or you could end up reusing the same metric without wanting it.
|
62
|
-
(this only applies to monitor_time and monitor_rate helpers but could apply anywhere else as needed)
|
63
|
-
|
64
|
-
# Usage
|
65
|
-
|
66
|
-
I try to keep things as simple as possible, there is currently two ways to use
|
67
|
-
this library:
|
68
|
-
|
69
|
-
- the first one is to just instantiate metrics by hand and use them directly
|
70
|
-
|
71
|
-
``` ruby
|
72
|
-
require 'drone'
|
73
|
-
Drone::init_drone()
|
74
|
-
@counter = Drone::Metris::Counter.new('my_counter')
|
75
|
-
|
76
|
-
def some_method
|
77
|
-
@counter.inc()
|
78
|
-
end
|
79
|
-
```
|
80
|
-
|
81
|
-
- the other way is to instrument a class:
|
82
|
-
|
83
|
-
``` ruby
|
84
|
-
require 'drone'
|
85
|
-
Drone::init_drone()
|
86
|
-
|
87
|
-
class User
|
88
|
-
include Drone::Monitoring
|
89
|
-
|
90
|
-
monitor_rate("users/new")
|
91
|
-
def initialize(login, pass); end
|
92
|
-
|
93
|
-
monitor_time("users/rename")
|
94
|
-
def rename(new_login); end
|
95
|
-
|
96
|
-
end
|
97
|
-
```
|
98
|
-
|
99
|
-
This code will create three metrics:
|
100
|
-
- "users/new" : how many users are created each second
|
101
|
-
- "users/rename" : how much time renaming a user takes and how many users are renamed
|
102
|
-
each second
|
103
|
-
|
104
|
-
|
105
|
-
Once you have your data you need to add a way to serve them, each lives in a separate
|
106
|
-
gem to limit the core's dependencies so the only one in core is:
|
107
|
-
|
108
|
-
- console output (puts), mainly for debug:
|
109
|
-
|
110
|
-
``` ruby
|
111
|
-
require 'drone'
|
112
|
-
Drone::init_drone()
|
113
|
-
Drone::add_output(:console, 1)
|
114
|
-
```
|
115
|
-
|
116
|
-
The values will be printed on the console at the inter
|
117
|
-
|
118
|
-
The others output are available in their own gems:
|
119
|
-
|
120
|
-
- drone_json:<br/>
|
121
|
-
The stats are served by a thin server in json
|
122
|
-
|
123
|
-
- drone_collectd:<br/>
|
124
|
-
The stats are send to a collectd daemon.
|
125
|
-
|
126
|
-
# Goals
|
127
|
-
|
128
|
-
My goal is to be able to serve stats efficiently from any ruby 1.9 application built
|
129
|
-
on top of eventmachine and fibers but I built the library to allow non eventmachine uses too, for
|
130
|
-
now the only part where eventmachine is required is the scheduler.
|
131
|
-
|
132
|
-
Implementing a scheduler based on a background Thread is possible but before that work
|
133
|
-
needs to be done to ensure thread safety, Actually the library is not thread safe.
|
134
|
-
|
135
|
-
if someone wants to implements it I am not against it but I prefer it to be added as an
|
136
|
-
optional part instead of in the core. There should not be any problem to implements it
|
137
|
-
in an includable module not included as default (it may requires some modifications in the core):
|
138
|
-
|
139
|
-
``` ruby
|
140
|
-
require 'drone'
|
141
|
-
require 'drone/threadsafe'
|
142
|
-
|
143
|
-
# [...]
|
144
|
-
```
|
145
|
-
|
146
|
-
# Development
|
147
|
-
|
148
|
-
Installing the development environment is pretty simple thanks to bundler:
|
149
|
-
|
150
|
-
gem install bundler
|
151
|
-
bundle
|
152
|
-
|
153
|
-
## Running specs
|
154
|
-
|
155
|
-
The specs are written with bacon, mocha and em-spec, they can be ran with:
|
156
|
-
|
157
|
-
rake spec
|
158
|
-
|
159
|
-
## Build the doc
|
160
|
-
You will need the gems: yard and bluecloth and then run:
|
161
|
-
|
162
|
-
rake doc
|
data/Rakefile
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
require 'bundler'
|
2
|
-
|
3
|
-
Bundler::GemHelper.install_tasks
|
4
|
-
|
5
|
-
task :spec do
|
6
|
-
ENV['COVERAGE'] = "1"
|
7
|
-
Dir.chdir( File.dirname(__FILE__) ) do
|
8
|
-
Dir["specs/**/*_spec.rb"].each do |path|
|
9
|
-
load(path)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
begin
|
15
|
-
require 'yard'
|
16
|
-
require 'bluecloth'
|
17
|
-
YARD::Rake::YardocTask.new(:doc)
|
18
|
-
rescue LoadError
|
19
|
-
|
20
|
-
end
|
data/drone.gemspec
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
$:.push File.expand_path("../lib", __FILE__)
|
3
|
-
require "drone/version"
|
4
|
-
|
5
|
-
Gem::Specification.new do |s|
|
6
|
-
s.name = "drone"
|
7
|
-
s.version = Drone::VERSION
|
8
|
-
s.platform = Gem::Platform::RUBY
|
9
|
-
s.authors = ["Julien Ammous"]
|
10
|
-
s.email = []
|
11
|
-
s.homepage = ""
|
12
|
-
s.summary = %q{Drone is a monitoring library}
|
13
|
-
s.description = %q{Drone is a monitoring library based on the metrics java library}
|
14
|
-
|
15
|
-
s.rubyforge_project = "drone"
|
16
|
-
|
17
|
-
s.files = `git ls-files`.split("\n")
|
18
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
-
s.require_paths = ["lib"]
|
21
|
-
|
22
|
-
s.add_dependency("eventmachine", "~> 0.12.10")
|
23
|
-
|
24
|
-
s.add_development_dependency("mocha")
|
25
|
-
s.add_development_dependency("bacon")
|
26
|
-
s.add_development_dependency("schmurfy-em-spec")
|
27
|
-
s.add_development_dependency("delorean")
|
28
|
-
s.add_development_dependency("simplecov")
|
29
|
-
end
|
data/examples/simple.rb
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
require File.expand_path('../common', __FILE__)
|
2
|
-
init_environment()
|
3
|
-
|
4
|
-
require 'fiber'
|
5
|
-
|
6
|
-
Drone::init_drone()
|
7
|
-
Drone::register_gauge("cpu:0/user"){ rand(200) }
|
8
|
-
|
9
|
-
class User
|
10
|
-
include Drone::Monitoring
|
11
|
-
|
12
|
-
def initialize(name)
|
13
|
-
@name = name
|
14
|
-
end
|
15
|
-
|
16
|
-
monitor_rate("users:rename:rate")
|
17
|
-
def rename(new_name)
|
18
|
-
@name = new_name
|
19
|
-
end
|
20
|
-
|
21
|
-
monitor_time("users:do_something:time")
|
22
|
-
monitor_rate("users:do_something:rate")
|
23
|
-
def do_something
|
24
|
-
# fb = Fiber.current
|
25
|
-
# EM::add_timer(1){ fb.resume() }
|
26
|
-
# Fiber.yield
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
Drone::add_output(:console, 1)
|
31
|
-
|
32
|
-
EM::run do
|
33
|
-
Drone::start_monitoring()
|
34
|
-
|
35
|
-
counter1 = Drone::register_counter("something_counted")
|
36
|
-
counter1.increment()
|
37
|
-
|
38
|
-
a = User.new("bob")
|
39
|
-
|
40
|
-
EM::add_periodic_timer(2) do
|
41
|
-
rand(100).times{|n| a.rename("user#{n}") }
|
42
|
-
counter1.increment()
|
43
|
-
end
|
44
|
-
|
45
|
-
EM::add_periodic_timer(2) do
|
46
|
-
Fiber.new do
|
47
|
-
a.do_something()
|
48
|
-
end.resume
|
49
|
-
end
|
50
|
-
end
|
data/lib/drone.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
|
2
|
-
module Drone
|
3
|
-
def self.require_lib(path)
|
4
|
-
require File.expand_path("../#{path}", __FILE__)
|
5
|
-
end
|
6
|
-
|
7
|
-
require_lib("drone/version")
|
8
|
-
|
9
|
-
require_lib("drone/monitoring")
|
10
|
-
|
11
|
-
# Schedulers
|
12
|
-
require_lib("drone/schedulers/eventmachine")
|
13
|
-
|
14
|
-
# Metrics
|
15
|
-
require_lib("drone/metrics/counter")
|
16
|
-
require_lib("drone/metrics/gauge")
|
17
|
-
require_lib("drone/metrics/histogram")
|
18
|
-
require_lib("drone/metrics/meter")
|
19
|
-
require_lib("drone/metrics/timer")
|
20
|
-
|
21
|
-
# Output
|
22
|
-
require_lib("drone/interfaces/console")
|
23
|
-
end
|
data/lib/drone/core.rb
DELETED
@@ -1,141 +0,0 @@
|
|
1
|
-
require 'forwardable'
|
2
|
-
|
3
|
-
|
4
|
-
require File.expand_path('../schedulers/eventmachine', __FILE__)
|
5
|
-
|
6
|
-
require File.expand_path('../storage/memory', __FILE__)
|
7
|
-
|
8
|
-
module Drone
|
9
|
-
##
|
10
|
-
# This module contains all the metrics you can use to collect data
|
11
|
-
#
|
12
|
-
module Metrics; end
|
13
|
-
|
14
|
-
|
15
|
-
##
|
16
|
-
# This module contains all the interfaces to the outside world,
|
17
|
-
# they are the only way to communicate with external applications
|
18
|
-
#
|
19
|
-
module Interface; end
|
20
|
-
|
21
|
-
|
22
|
-
##
|
23
|
-
# This module contains the class used for scheduling timers
|
24
|
-
#
|
25
|
-
module Schedulers; end
|
26
|
-
|
27
|
-
|
28
|
-
##
|
29
|
-
# This module contains the class used for storage,
|
30
|
-
# they determine where the metric's data are stored
|
31
|
-
#
|
32
|
-
module Storage; end
|
33
|
-
|
34
|
-
class <<self
|
35
|
-
extend Forwardable
|
36
|
-
|
37
|
-
def init_drone(scheduler = Schedulers::EMScheduler, storage = Storage::Memory.new)
|
38
|
-
@metrics = []
|
39
|
-
@scheduler = scheduler
|
40
|
-
@storage = storage
|
41
|
-
@monitored_classes = []
|
42
|
-
@output_modules = []
|
43
|
-
end
|
44
|
-
|
45
|
-
##
|
46
|
-
# Start monitoring.
|
47
|
-
# This method needs to be called when the timers can be started
|
48
|
-
# In the case of eventmachine scheduler it needs to be called
|
49
|
-
# in the EM::run block
|
50
|
-
#
|
51
|
-
def start_monitoring
|
52
|
-
@scheduler.start()
|
53
|
-
end
|
54
|
-
|
55
|
-
def each_metric
|
56
|
-
raise "Block expected" unless block_given?
|
57
|
-
@metrics.each{|m| yield(m) }
|
58
|
-
end
|
59
|
-
|
60
|
-
|
61
|
-
##
|
62
|
-
# Fetch a metric by its name
|
63
|
-
#
|
64
|
-
# @param [String] name The mtric's name
|
65
|
-
#
|
66
|
-
def find_metric(name)
|
67
|
-
@metrics.detect{|m| m.name == name }
|
68
|
-
end
|
69
|
-
|
70
|
-
##
|
71
|
-
# Instantiate an output module.
|
72
|
-
#
|
73
|
-
# @param [String,Symbol] type Class name in lowercase
|
74
|
-
# @param [Array] args additional parameters will be sent to thh
|
75
|
-
# class constructor
|
76
|
-
#
|
77
|
-
def add_output(type, *args)
|
78
|
-
class_name = type.to_s.capitalize
|
79
|
-
klass = Drone::Interfaces.const_get(class_name)
|
80
|
-
@output_modules << klass.new(*args)
|
81
|
-
end
|
82
|
-
|
83
|
-
##
|
84
|
-
# Register a new counter
|
85
|
-
# @see Drone::Metrics::Counter
|
86
|
-
# @param [String] type Name of this metric
|
87
|
-
# @api public
|
88
|
-
#
|
89
|
-
def register_counter(type)
|
90
|
-
register_metric( Drone::Metrics::Counter.new(type) )
|
91
|
-
end
|
92
|
-
|
93
|
-
|
94
|
-
##
|
95
|
-
# Register an Histogram
|
96
|
-
# @see Drone::Metrics::Histogram
|
97
|
-
# @param [String] name Name of this metric
|
98
|
-
# @param [optional,Enum] type one of Drone::Metrics::Histogram::TYPE_UNIFORM or Drone::Metrics::Histogram::TYPE_BIASED
|
99
|
-
#
|
100
|
-
def register_histogram(name, type = :uniform)
|
101
|
-
register_metric( Drone::Metrics::Histogram.new(name, type) )
|
102
|
-
end
|
103
|
-
|
104
|
-
##
|
105
|
-
# Register a new gauge
|
106
|
-
# @see Drone::Metrics::Gauge
|
107
|
-
# @param [String] type Name of this metric
|
108
|
-
# @api public
|
109
|
-
#
|
110
|
-
def register_gauge(type, &block)
|
111
|
-
register_metric( Drone::Metrics::Gauge.new(type, &block) )
|
112
|
-
end
|
113
|
-
|
114
|
-
##
|
115
|
-
# Register a new metric
|
116
|
-
# This method can be used bu the user but the prefered
|
117
|
-
# way is to use the register_counter / register_gauge methods
|
118
|
-
#
|
119
|
-
# @param [Metric] metric The Metric to register
|
120
|
-
# @private
|
121
|
-
#
|
122
|
-
def register_metric(metric)
|
123
|
-
@metrics << metric
|
124
|
-
metric
|
125
|
-
end
|
126
|
-
|
127
|
-
|
128
|
-
def_delegators :@storage, :request_fixed_size_array, :request_number, :request_hash
|
129
|
-
def_delegators :@scheduler, :schedule_periodic, :schedule_once
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
##
|
134
|
-
# Register a monitored class.
|
135
|
-
# @private
|
136
|
-
#
|
137
|
-
def register_monitored_class(klass)
|
138
|
-
@monitored_classes << klass
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|