insight_agent 0.0.7
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.
- data/.gemtest +0 -0
- data/History.txt +4 -0
- data/Manifest.txt +23 -0
- data/PostInstall.txt +7 -0
- data/README.rdoc +37 -0
- data/Rakefile +28 -0
- data/lib/agent/event_tree.rb +120 -0
- data/lib/agent/frame.rb +35 -0
- data/lib/agent/frame_builder.rb +83 -0
- data/lib/agent/insight_agent.rb +59 -0
- data/lib/agent/method_operation.rb +26 -0
- data/lib/agent/operation.rb +16 -0
- data/lib/agent/trace.rb +20 -0
- data/lib/agent/trace_builder.rb +28 -0
- data/lib/agent/trace_dispatcher.rb +84 -0
- data/lib/agent/trace_queue.rb +22 -0
- data/lib/agent/wrap_method.rb +55 -0
- data/lib/agent.rb +19 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/test_agent.rb +72 -0
- data/test/test_event_tree.rb +80 -0
- data/test/test_frame_builder.rb +18 -0
- data/test/test_helper.rb +3 -0
- data/test/test_wrap_method.rb +110 -0
- metadata +105 -0
data/.gemtest
ADDED
File without changes
|
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
PostInstall.txt
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
lib/agent.rb
|
7
|
+
lib/agent/frame.rb
|
8
|
+
lib/agent/frame.rb
|
9
|
+
lib/agent/frame_builder.rb
|
10
|
+
lib/agent/operation.rb
|
11
|
+
lib/agent/trace.rb
|
12
|
+
lib/agent/trace_builder.rb
|
13
|
+
lib/agent/trace_dispatcher.rb
|
14
|
+
lib/agent/trace_queue.rb
|
15
|
+
lib/agent/insight_agent.rb
|
16
|
+
lib/agent/event_tree.rb
|
17
|
+
lib/agent/wrap_method.rb
|
18
|
+
lib/agent/method_operation.rb
|
19
|
+
script/console
|
20
|
+
script/destroy
|
21
|
+
script/generate
|
22
|
+
test/test_agent.rb
|
23
|
+
test/test_helper.rb
|
data/PostInstall.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
= insight_agent
|
2
|
+
|
3
|
+
* http://git.springsource.com/insight
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Provides preliminary support for rails applications for
|
8
|
+
Insight
|
9
|
+
|
10
|
+
== FEATURES/PROBLEMS:
|
11
|
+
|
12
|
+
* Need to change the module name from Agent to InsightAgent
|
13
|
+
* Not all events are captured
|
14
|
+
* events which do not have a "closing" event may stick around
|
15
|
+
in an eventtree permenantly
|
16
|
+
* REST is not the best
|
17
|
+
|
18
|
+
== SYNOPSIS:
|
19
|
+
|
20
|
+
Modify guestbook.demo/Gemfile add the line:
|
21
|
+
gem 'insight_agent'
|
22
|
+
|
23
|
+
Add the agent/integration_test/rails_init.rb to
|
24
|
+
the guestbook.demo/config/initializers directory
|
25
|
+
and customize
|
26
|
+
|
27
|
+
== REQUIREMENTS:
|
28
|
+
|
29
|
+
* Rails 3.0
|
30
|
+
|
31
|
+
== INSTALL:
|
32
|
+
|
33
|
+
* sudo gem install insight_agent
|
34
|
+
|
35
|
+
== LICENSE:
|
36
|
+
|
37
|
+
Copyright VMware 2011
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
gem 'hoe', '>= 2.1.0'
|
4
|
+
require 'hoe'
|
5
|
+
require 'fileutils'
|
6
|
+
require './lib/agent'
|
7
|
+
|
8
|
+
Hoe.plugin :newgem
|
9
|
+
# Hoe.plugin :website
|
10
|
+
# Hoe.plugin :cucumberfeatures
|
11
|
+
|
12
|
+
# Generate all the Rake tasks
|
13
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
14
|
+
$hoe = Hoe.spec 'insight_agent' do
|
15
|
+
self.developer 'John V Kew', 'jkew@vmware.com'
|
16
|
+
self.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
|
17
|
+
self.rubyforge_name = self.name # TODO this is default value
|
18
|
+
self.summary = 'An agent for Insight'
|
19
|
+
self.description = 'This agent will report traces to Insight'
|
20
|
+
self.extra_deps = [['activesupport','>= 3.0.0']]
|
21
|
+
end
|
22
|
+
|
23
|
+
# require 'newgem/tasks'
|
24
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
25
|
+
|
26
|
+
# TODO - want other tests/tasks run by default? Add them to the list
|
27
|
+
# remove_task :default
|
28
|
+
# task :default => [:spec, :features]
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# Constructs a frame tree from a series of
|
2
|
+
# related ActiveSupport::events
|
3
|
+
class EventTree
|
4
|
+
class Event
|
5
|
+
@name = nil
|
6
|
+
@label = nil
|
7
|
+
@startTime = nil
|
8
|
+
@endTime = nil
|
9
|
+
@invertEndTime = nil
|
10
|
+
@duration = nil
|
11
|
+
@payload = {}
|
12
|
+
def initialize(name, label, startTime, endTime, payload)
|
13
|
+
if startTime > endTime
|
14
|
+
raise "Invalid event, startTime is greater than endTime " + startTime.to_s + "->" + endTime.to_s
|
15
|
+
end
|
16
|
+
@name = name
|
17
|
+
@label = label
|
18
|
+
@startTime = startTime
|
19
|
+
@endTime = endTime
|
20
|
+
@payload = payload
|
21
|
+
@invertEndTime = - endTime.to_f
|
22
|
+
@duration = endTime - startTime
|
23
|
+
end
|
24
|
+
attr_accessor :name, :label, :duration, :invertEndTime, :startTime, :endTime, :payload
|
25
|
+
|
26
|
+
def contains(event)
|
27
|
+
event.startTime >= @startTime && event.endTime <= @endTime
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# To separate lists are maintained, events is sorted by
|
32
|
+
# start time and duration, and eventsEnd
|
33
|
+
# by only endTime.
|
34
|
+
@events = []
|
35
|
+
@eventsEnd = []
|
36
|
+
@@DEFAULT_EVENT = "DEFAULT"
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
@events = []
|
40
|
+
@eventsEnd = []
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_accessor :DEFAULT_EVENT
|
44
|
+
|
45
|
+
# Prefer event with the longest duration
|
46
|
+
def chooseBestDefaultLabel(eventA, eventB)
|
47
|
+
if eventA.duration > eventB.duration
|
48
|
+
return eventA.label
|
49
|
+
end
|
50
|
+
return eventB.label
|
51
|
+
end
|
52
|
+
|
53
|
+
# Add an event to this tree
|
54
|
+
def addEvent(name, label, startTime, endTime, payload = {})
|
55
|
+
e = Event.new(name, label, startTime, endTime, payload)
|
56
|
+
@events << e
|
57
|
+
@events = @events.sort_by { |a| [ a.startTime, a.invertEndTime ] }
|
58
|
+
@eventsEnd << e
|
59
|
+
@eventsEnd = @eventsEnd.sort_by { |a| [ a.endTime, a.duration ]}
|
60
|
+
end
|
61
|
+
|
62
|
+
def dumpFrames
|
63
|
+
FrameBuilder.instance.reset
|
64
|
+
if @events == nil
|
65
|
+
raise "Trying to dump frame stack before any events collected"
|
66
|
+
end
|
67
|
+
|
68
|
+
# if needed, create a wrapper event if the events do not have
|
69
|
+
# a spanning parent
|
70
|
+
firstEvent = @events.first
|
71
|
+
lastEvent = @eventsEnd.last
|
72
|
+
if firstEvent != lastEvent
|
73
|
+
firstStartTime = @events.first.startTime
|
74
|
+
lastEndTime = @eventsEnd.last.endTime
|
75
|
+
puts " NEW DEFAULT " + firstStartTime.to_f.to_s + "->" + lastEndTime.to_f.to_s
|
76
|
+
label = chooseBestDefaultLabel(firstEvent, lastEvent)
|
77
|
+
@events << Event.new(@@DEFAULT_EVENT, label, firstStartTime, lastEndTime, {})
|
78
|
+
@events = @events.sort_by { |a| [ a.startTime, a.invertEndTime ] }
|
79
|
+
end
|
80
|
+
|
81
|
+
# run through the event list
|
82
|
+
stack = []
|
83
|
+
@events.each {|e|
|
84
|
+
|
85
|
+
# Create the root
|
86
|
+
if stack.size == 0
|
87
|
+
op = Operation.new(e.payload, e.label, e.name)
|
88
|
+
FrameBuilder.instance.enter(op, e.startTime)
|
89
|
+
stack << e
|
90
|
+
next
|
91
|
+
end
|
92
|
+
|
93
|
+
# If the last event on the stack cannot contain this one, crawl
|
94
|
+
# up the stack until we find one that does
|
95
|
+
while stack.size > 1 && !stack.last.contains(e)
|
96
|
+
FrameBuilder.instance.exit(stack.last.endTime, false)
|
97
|
+
stack.pop
|
98
|
+
end
|
99
|
+
|
100
|
+
# Add the event, enter the frame
|
101
|
+
op = Operation.new(e.payload, e.label, e.name)
|
102
|
+
FrameBuilder.instance.enter(op, e.startTime)
|
103
|
+
stack << e
|
104
|
+
}
|
105
|
+
|
106
|
+
# crawl back up the stack
|
107
|
+
while stack.size > 1
|
108
|
+
FrameBuilder.instance.exit(stack.last.endTime, false)
|
109
|
+
stack.pop
|
110
|
+
end
|
111
|
+
|
112
|
+
# final exit
|
113
|
+
trace = FrameBuilder.instance.exit(stack.last.endTime, false)
|
114
|
+
if trace != nil
|
115
|
+
return trace
|
116
|
+
end
|
117
|
+
raise "Unable to create complete frame stack from events. Your algorithm is broken."
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
data/lib/agent/frame.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Ruby version of a Frame Object
|
2
|
+
# Self-time is not maintained here, only start and end time
|
3
|
+
class Frame
|
4
|
+
@id = 1
|
5
|
+
@startNanos = 0
|
6
|
+
@endNanos = 0
|
7
|
+
@operation = nil
|
8
|
+
@parent = nil
|
9
|
+
@children = []
|
10
|
+
|
11
|
+
def initialize(id, startNanos, endNanos, op, parentFrame = nil, childFrames=[])
|
12
|
+
@id = id
|
13
|
+
@startNanos = startNanos
|
14
|
+
@endNanos = endNanos
|
15
|
+
@operation = op
|
16
|
+
@parent = parentFrame
|
17
|
+
@children = childFrames
|
18
|
+
if @startNanos >= @endNanos
|
19
|
+
raise(ArgumentError, "StartNanos " + ("%d" % @startNanos) + " must be less than EndNanos " + ("%d" % @endNanos), Kernel.caller)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_accessor :startNanos, :endNanos, :operation, :parent, :children
|
24
|
+
|
25
|
+
# Used to handle the circular reference for the parent frame
|
26
|
+
def as_json(*a)
|
27
|
+
{
|
28
|
+
'id' => @id,
|
29
|
+
'startNanos' => "%d" % @startNanos, # must not be in scientific notation
|
30
|
+
'endNanos' => "%d" % @endNanos,
|
31
|
+
'operation' => @operation,
|
32
|
+
'children' => @children
|
33
|
+
}.as_json(*a)
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Ruby version of the Java FrameBuilder Contructs a tree
|
2
|
+
# of frames through calls to enter and exit. One FrameBuilder
|
3
|
+
# is maintained per thread.
|
4
|
+
class FrameBuilder
|
5
|
+
@last = nil
|
6
|
+
|
7
|
+
def initialize()
|
8
|
+
@id = 0
|
9
|
+
@depth = 0
|
10
|
+
@current = nil
|
11
|
+
@parent = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :last, :depth
|
15
|
+
|
16
|
+
def self.instance
|
17
|
+
instance = Thread.current['instance']
|
18
|
+
if instance == nil
|
19
|
+
instance = Thread.current['instance'] = FrameBuilder.new
|
20
|
+
puts "Creating new instance of frame builder for thread " + Thread.current.object_id.to_s
|
21
|
+
end
|
22
|
+
return instance
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def createFrame(id, operation, startTime, endTime, children = [])
|
27
|
+
startTimeNanos = startTime.to_f * 1000000000
|
28
|
+
endTimeNanos = endTime.to_f * 1000000000
|
29
|
+
if endTimeNanos.to_i <= startTimeNanos.to_i
|
30
|
+
endTimeNanos = startTimeNanos + 1000
|
31
|
+
end
|
32
|
+
Frame.new(id, startTimeNanos, endTimeNanos, operation, nil, children)
|
33
|
+
end
|
34
|
+
|
35
|
+
def enter(operation, time = Time.now )
|
36
|
+
startTimeNanos = time.to_f * 1000000000
|
37
|
+
@id += 1
|
38
|
+
@depth += 1
|
39
|
+
@parent = @current
|
40
|
+
initialEndTime = startTimeNanos + 1000
|
41
|
+
@current = Frame.new(@id, startTimeNanos, initialEndTime, operation, @parent, [])
|
42
|
+
end
|
43
|
+
|
44
|
+
def exit(time = Time.now, addTrace = true)
|
45
|
+
endTimeNanos = time.to_f * 1000000000
|
46
|
+
|
47
|
+
@depth -= 1
|
48
|
+
@current.endNanos = endTimeNanos
|
49
|
+
if @current.endNanos < @current.startNanos
|
50
|
+
raise "end time less than or equal to start time for frame " + @current.operation.label + "StartNanos " + ("%d" % @current.startNanos) + " must be less than EndNanos " + ("%d" % @current.endNanos)
|
51
|
+
end
|
52
|
+
|
53
|
+
# At root already
|
54
|
+
if @parent == nil
|
55
|
+
trace = TraceBuilder.instance.createTrace(@current)
|
56
|
+
if addTrace
|
57
|
+
TraceQueue.instance.addTrace(trace)
|
58
|
+
end
|
59
|
+
@last = @current
|
60
|
+
reset
|
61
|
+
trace
|
62
|
+
else
|
63
|
+
@parent.children << @current
|
64
|
+
@current = @parent
|
65
|
+
@parent = @current.parent
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def reset()
|
71
|
+
@id = 0
|
72
|
+
@depth = 0
|
73
|
+
@current = nil
|
74
|
+
@parent = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def lastStart
|
79
|
+
if last != null
|
80
|
+
@last.startNanos / 1000000
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Rails entry point for the insight-agent
|
2
|
+
class InsightAgent
|
3
|
+
@@dispatcher = nil
|
4
|
+
|
5
|
+
# Hash of eventId => EventTree
|
6
|
+
@@events = {}
|
7
|
+
def initialize(app, url, user, pass)
|
8
|
+
@@dispatcher = TraceDispatcher.new(url, user, pass)
|
9
|
+
TraceBuilder.instance.setApplication(app)
|
10
|
+
@@dispatcher.start
|
11
|
+
puts "dispatcher started"
|
12
|
+
initialize_active_support()
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize_plugins()
|
16
|
+
# load a series of plugins from a known location
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize_active_support()
|
21
|
+
# Subscribe to every event in the system
|
22
|
+
ActiveSupport::Notifications.subscribe do |name, start, finish, id, payload|
|
23
|
+
#puts id + " " + name + " " + start.to_f.to_s + "->" + finish.to_f.to_s
|
24
|
+
eventTree = @@events[id]
|
25
|
+
if eventTree == nil
|
26
|
+
eventTree = EventTree.new
|
27
|
+
@@events[id] = eventTree
|
28
|
+
end
|
29
|
+
|
30
|
+
# All properties should be simple strings
|
31
|
+
properties = {}
|
32
|
+
payload.each { |key, value| properties[key] = value.to_s }
|
33
|
+
|
34
|
+
# Effectively endpoint analyzers; events are collected by id
|
35
|
+
# until the event with the name, "process_action.action_controller"
|
36
|
+
# at that point the events are dumped into a trace/frame stack and
|
37
|
+
# stored on the TraceQueue.
|
38
|
+
# TODO:
|
39
|
+
# - Deletes on the model do not seem to be picked up here
|
40
|
+
# - If a series of events are collected which do not end
|
41
|
+
# with this named event, a dud EventTree could be sitting
|
42
|
+
# around in the hash
|
43
|
+
if name == "process_action.action_controller"
|
44
|
+
label = payload[:path]
|
45
|
+
eventTree.addEvent(name, label, start, finish, properties)
|
46
|
+
trace = eventTree.dumpFrames
|
47
|
+
TraceQueue.instance.addTrace(trace)
|
48
|
+
@@events[id] = nil
|
49
|
+
else
|
50
|
+
eventTree.addEvent(name, name, start, finish, properties)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def stop
|
57
|
+
@@dispatcher.stop
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class MethodOperation
|
2
|
+
|
3
|
+
def initialize(clazz, methodName)
|
4
|
+
WrapMethod.instance.wrap(clazz, methodName, createOperation)
|
5
|
+
end
|
6
|
+
|
7
|
+
def createOperation(point, clazz, method, args)
|
8
|
+
if point == :enter
|
9
|
+
properties = {}
|
10
|
+
if args != nil
|
11
|
+
index = 0
|
12
|
+
args.each do |arg|
|
13
|
+
properties["arg" + index.to_s] = arg.to_s
|
14
|
+
index += 1
|
15
|
+
end
|
16
|
+
end
|
17
|
+
label = clazz.to_s + ":" + method.to_s
|
18
|
+
op = Operation.new(properties, label, label)
|
19
|
+
FrameBuilder.instance.enter(op)
|
20
|
+
end
|
21
|
+
if point == :exit
|
22
|
+
FrameBuilder.instance.exit()
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Ruby version of the Operation
|
2
|
+
class Operation
|
3
|
+
@properties = {}
|
4
|
+
@label = ""
|
5
|
+
@operationType = ""
|
6
|
+
|
7
|
+
def initialize(properties, label, operationType)
|
8
|
+
@properties = properties
|
9
|
+
@label = label
|
10
|
+
@operationType = operationType
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :label, :operationType
|
14
|
+
|
15
|
+
|
16
|
+
end
|
data/lib/agent/trace.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Ruby version of a Trace
|
2
|
+
class Trace
|
3
|
+
@id = nil
|
4
|
+
@date = nil
|
5
|
+
@server = nil
|
6
|
+
@root = nil
|
7
|
+
@appName = nil
|
8
|
+
|
9
|
+
attr_accessor :root
|
10
|
+
|
11
|
+
def initialize(id, date, server, appName, root)
|
12
|
+
@id = id
|
13
|
+
@date = date
|
14
|
+
@server = server
|
15
|
+
@appName = appName
|
16
|
+
@root = root
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Builds a trace using configured values for the server, pid, and application
|
2
|
+
require 'monitor'
|
3
|
+
require 'singleton'
|
4
|
+
require "socket"
|
5
|
+
|
6
|
+
class TraceBuilder < Monitor
|
7
|
+
include Singleton
|
8
|
+
@@server = Socket.gethostname
|
9
|
+
@@pid = Process.pid.to_s
|
10
|
+
@@application = "myapplication"
|
11
|
+
@@id = 0
|
12
|
+
@@random = rand(10000).to_s
|
13
|
+
|
14
|
+
def createTrace(frame)
|
15
|
+
synchronize do
|
16
|
+
@@id += 1
|
17
|
+
Trace.new(@@random + "-" + @@id.to_s, frame.startNanos, @@server + "-" + @@pid, @@application, frame)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def setApplication(app)
|
22
|
+
@@application = app
|
23
|
+
end
|
24
|
+
|
25
|
+
def setServer(srv)
|
26
|
+
@@server = srv
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# Simplistic, asynchronous trace dispatcher. Pulls
|
2
|
+
# traces off the TraceQueue and sends them along to
|
3
|
+
# Spring Insight over REST as JSON
|
4
|
+
#
|
5
|
+
# Requires ActiveSupport::JSON
|
6
|
+
# The rails JSON is used here due to incompatibilities
|
7
|
+
# when using the non-rails JSON inside of Rails
|
8
|
+
# ActiveSupport::JSON overrides JSON behaviour in wierd
|
9
|
+
# ways
|
10
|
+
class TraceDispatcher
|
11
|
+
require 'net/http'
|
12
|
+
require 'rubygems'
|
13
|
+
require 'active_support/json'
|
14
|
+
require 'active_support/ordered_hash'
|
15
|
+
|
16
|
+
@host = "localhost"
|
17
|
+
@port = 8080
|
18
|
+
@user = ""
|
19
|
+
@pass = ""
|
20
|
+
@thread = nil
|
21
|
+
@running = false
|
22
|
+
|
23
|
+
def initialize(url, user, pass)
|
24
|
+
uri = URI.parse(url)
|
25
|
+
@host = uri.host
|
26
|
+
@port = uri.port
|
27
|
+
@user = user
|
28
|
+
@pass = pass
|
29
|
+
end
|
30
|
+
|
31
|
+
public
|
32
|
+
|
33
|
+
def start()
|
34
|
+
@running = true
|
35
|
+
@thread = Thread.new {
|
36
|
+
while @running do
|
37
|
+
while poll() do
|
38
|
+
## noop
|
39
|
+
end
|
40
|
+
sleep(0.1)
|
41
|
+
end
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def stop()
|
46
|
+
if @thread != nil
|
47
|
+
@running = false
|
48
|
+
@thread.join
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def poll()
|
53
|
+
trace = TraceQueue.instance.getTrace()
|
54
|
+
if trace != nil
|
55
|
+
send(trace)
|
56
|
+
true
|
57
|
+
else
|
58
|
+
false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.convert_to_json(trace)
|
63
|
+
trace.to_json
|
64
|
+
end
|
65
|
+
|
66
|
+
def send(trace)
|
67
|
+
dispatch(@host, @port, @user, @pass, TraceDispatcher.convert_to_json(trace))
|
68
|
+
end
|
69
|
+
|
70
|
+
def dispatch(host, port, user, pass, traceJSON)
|
71
|
+
puts traceJSON
|
72
|
+
path="/insight/services/traces/rest/dispatch"
|
73
|
+
headers = {'Content-Type' =>'application/json'}
|
74
|
+
http = Net::HTTP.new(host, port)
|
75
|
+
req = Net::HTTP::Put.new(path,headers)
|
76
|
+
req.basic_auth user, pass
|
77
|
+
req.body = traceJSON
|
78
|
+
res, data = http.request(req)
|
79
|
+
puts data
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Maintains a simple queue of traces
|
2
|
+
# We want to use the Ruby Queue implementation
|
3
|
+
# here, except for this:
|
4
|
+
# http://redmine.ruby-lang.org/issues/2092
|
5
|
+
require 'singleton'
|
6
|
+
require 'monitor'
|
7
|
+
|
8
|
+
class TraceQueue < Monitor
|
9
|
+
include Singleton
|
10
|
+
@@trace_queue = []
|
11
|
+
def addTrace(trace)
|
12
|
+
synchronize do
|
13
|
+
@@trace_queue << trace
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def getTrace()
|
18
|
+
synchronize do
|
19
|
+
@@trace_queue.shift
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Worthy ideas for this were taken from
|
2
|
+
# https://github.com/jordansissel/ruby-minstrel/blob/master/lib/minstrel.rb
|
3
|
+
require 'singleton'
|
4
|
+
require 'monitor'
|
5
|
+
|
6
|
+
class WrapMethod < Monitor
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
@@wrapped = []
|
10
|
+
|
11
|
+
def key(clazz, method)
|
12
|
+
clazz.to_s + ":" + method.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def wrap(clazz, methodName, &block)
|
16
|
+
methodKey = key(clazz, methodName)
|
17
|
+
if @@wrapped.index(methodKey) != nil
|
18
|
+
raise methodKey + " Previously wrapped"
|
19
|
+
end
|
20
|
+
synchronize do
|
21
|
+
clazz.instance_methods.each do |inst_method|
|
22
|
+
if inst_method == methodName
|
23
|
+
# crack the class
|
24
|
+
clazz.instance_eval do
|
25
|
+
# rename the original method
|
26
|
+
inst_method_sym = inst_method.to_sym
|
27
|
+
renamed_inst_method_sym = ("insight_wrapped_" + inst_method).to_sym
|
28
|
+
puts "Aliasing " + inst_method.to_s + " -> " + renamed_inst_method_sym.to_s + "\n"
|
29
|
+
alias_method renamed_inst_method_sym, inst_method_sym
|
30
|
+
insight_method_name = ("insight_wrapper_" + inst_method).to_sym
|
31
|
+
|
32
|
+
# Define a new method which executes our code at the beginning
|
33
|
+
# and end
|
34
|
+
puts "Creating " + insight_method_name.to_s + "\n"
|
35
|
+
define_method insight_method_name do |*args, &argblock|
|
36
|
+
block.call(:enter, clazz, inst_method, *args)
|
37
|
+
if renamed_inst_method_sym.is_a?(Symbol)
|
38
|
+
val = send(renamed_inst_method_sym, *args, &argblock)
|
39
|
+
else
|
40
|
+
val = renamed_inst_method_sym.call(*args, &argblock)
|
41
|
+
end
|
42
|
+
block.call(:exit, clazz, inst_method, *args)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Alias our method over the original one
|
46
|
+
puts "Aliasing " + inst_method.to_s + " -> " + insight_method_name.to_s + "\n"
|
47
|
+
alias_method inst_method_sym, insight_method_name
|
48
|
+
end
|
49
|
+
@@wrapped << methodKey
|
50
|
+
@@wrapped.sort
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/agent.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
require 'agent/frame'
|
4
|
+
require 'agent/frame_builder.rb'
|
5
|
+
require 'agent/operation.rb'
|
6
|
+
require 'agent/trace.rb'
|
7
|
+
require 'agent/frame_builder.rb'
|
8
|
+
require 'agent/trace_dispatcher.rb'
|
9
|
+
require 'agent/trace_queue.rb'
|
10
|
+
require 'agent/trace_builder.rb'
|
11
|
+
require 'agent/insight_agent.rb'
|
12
|
+
require 'agent/event_tree.rb'
|
13
|
+
require 'agent/wrap_method.rb'
|
14
|
+
require 'agent/method_operation.rb'
|
15
|
+
|
16
|
+
module Agent
|
17
|
+
VERSION = '0.0.7'
|
18
|
+
end
|
19
|
+
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/agent.rb'}"
|
9
|
+
puts "Loading agent gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
data/test/test_agent.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
require 'stringio'
|
3
|
+
require 'test/unit'
|
4
|
+
require File.dirname(__FILE__) + '/../lib/agent'
|
5
|
+
|
6
|
+
class TraceDispatcher
|
7
|
+
@@count = 0
|
8
|
+
def getDispatchCount()
|
9
|
+
@@count
|
10
|
+
end
|
11
|
+
|
12
|
+
def dispatch(host, port, user, pass, trace)
|
13
|
+
@@count += 1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class TestAgent < Test::Unit::TestCase
|
18
|
+
|
19
|
+
def setup
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_trace_flow
|
23
|
+
salt="test"
|
24
|
+
dispatcher = TraceDispatcher.new("http://localhost:8080", "admin", "insight")
|
25
|
+
|
26
|
+
builder = TraceBuilder.instance
|
27
|
+
builder.setApplication("App" + salt)
|
28
|
+
builder.setServer("Svr" + salt)
|
29
|
+
|
30
|
+
t1 = Thread.new { 4.times { createTrace(salt) } }
|
31
|
+
t2 = Thread.new { 4.times { createTrace(salt) } }
|
32
|
+
t3 = Thread.new { 4.times { createTrace(salt) } }
|
33
|
+
t4 = Thread.new { 4.times { createTrace(salt) } }
|
34
|
+
dispatcher.start
|
35
|
+
t1.join
|
36
|
+
t2.join
|
37
|
+
t3.join
|
38
|
+
t4.join
|
39
|
+
sleep(0.2)
|
40
|
+
dispatcher.stop
|
41
|
+
assert_equal(16,dispatcher.getDispatchCount())
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def fakeMethod(salt)
|
48
|
+
op = Operation.new({}, "fakeMethod" + salt, "SuperOperation" + salt)
|
49
|
+
FrameBuilder.instance.enter op
|
50
|
+
sleep(0.2)
|
51
|
+
fakeSubMethod salt
|
52
|
+
sleep(0.1)
|
53
|
+
FrameBuilder.instance.exit
|
54
|
+
end
|
55
|
+
|
56
|
+
def fakeSubMethod(salt)
|
57
|
+
op = Operation.new({}, "fakeSubMethod" + salt, "PunyOperation" + salt)
|
58
|
+
FrameBuilder.instance.enter op
|
59
|
+
sleep(0.1)
|
60
|
+
FrameBuilder.instance.exit
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def createTrace(salt)
|
65
|
+
mysalt = salt + Thread.current.object_id.to_s
|
66
|
+
fakeMethod mysalt
|
67
|
+
frame = FrameBuilder.instance.last
|
68
|
+
trace = Trace.new(salt + "worthy-9", frame.startNanos, "MyServer" + salt, "MyApp" + salt, frame)
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
require 'stringio'
|
3
|
+
require 'test/unit'
|
4
|
+
require File.dirname(__FILE__) + '/../lib/agent'
|
5
|
+
require 'active_support/json'
|
6
|
+
require 'active_support/ordered_hash'
|
7
|
+
|
8
|
+
class TestFrameBuilder < Test::Unit::TestCase
|
9
|
+
|
10
|
+
def setup
|
11
|
+
end
|
12
|
+
|
13
|
+
def printTree(frame, depth)
|
14
|
+
ot = frame.operation.operationType
|
15
|
+
printf("%" + depth.to_s + "s %d\n",ot, depth)
|
16
|
+
children = frame.children
|
17
|
+
children.each {|c| printTree(c, 1 + depth)}
|
18
|
+
end
|
19
|
+
|
20
|
+
def isCorrect(frame, map)
|
21
|
+
ot = frame.operation.operationType
|
22
|
+
assert(map[ot] != nil, "Frame with operation " + ot + " is not in map " + map.to_s)
|
23
|
+
children = frame.children
|
24
|
+
children.each {|c| isCorrect(c, map[ot])}
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_event_tree_normal_order
|
28
|
+
et = EventTree.new
|
29
|
+
et.addEvent("B", "B Label", Time.at(5), Time.at(20))
|
30
|
+
et.addEvent("C", "C Label",Time.at(22), Time.at(80))
|
31
|
+
et.addEvent("D", "D Label",Time.at(5), Time.at(90))
|
32
|
+
et.addEvent("E", "E Label",Time.at(92), Time.at(95))
|
33
|
+
et.addEvent("A", "A Label",Time.at(0), Time.at(100))
|
34
|
+
trace = et.dumpFrames
|
35
|
+
expected = {"A" => {"D" => {"B" => {}, "C" => {}}, "E" => {}}}
|
36
|
+
isCorrect(trace.root, expected)
|
37
|
+
end
|
38
|
+
|
39
|
+
# The root of this should be the DEFAULT operation type,
|
40
|
+
# formed from the rootless remains of other out-of-order
|
41
|
+
# events
|
42
|
+
def test_event_tree_wierd_order
|
43
|
+
et = EventTree.new
|
44
|
+
et.addEvent("B", "B Label",Time.at(5), Time.at(20))
|
45
|
+
et.addEvent("A", "A Label",Time.at(0), Time.at(100))
|
46
|
+
et.addEvent("E", "E Label",Time.at(92), Time.at(95))
|
47
|
+
et.addEvent("C", "C Label",Time.at(22), Time.at(80))
|
48
|
+
et.addEvent("D", "D Label",Time.at(5), Time.at(90))
|
49
|
+
trace = et.dumpFrames
|
50
|
+
puts "\n"
|
51
|
+
printTree(trace.root, 1)
|
52
|
+
|
53
|
+
#INSIGHT_STUB 0
|
54
|
+
# A 1
|
55
|
+
# B 2
|
56
|
+
# E 1
|
57
|
+
# C 1
|
58
|
+
# D 1
|
59
|
+
expected = {"A" => {"D" => {"B" => {}, "C" => {}}, "E" => {}}}
|
60
|
+
|
61
|
+
#expected = {"DEFAULT" => {"A" => {"B" => {}}, "E" => {}, "C" => {}, "D" => {}}}
|
62
|
+
isCorrect(trace.root, expected)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_overlap
|
66
|
+
et = EventTree.new
|
67
|
+
et.addEvent("S", "S-start_processing.action_controller", Time.at(1298858931.01111), Time.at(1298858931.01111))
|
68
|
+
et.addEvent("SQL", "SQL-sql.active_record", Time.at(1298858931.06312), Time.at(1298858931.10826))
|
69
|
+
et.addEvent("R1", "R1-!render_template.action_view", Time.at(1298858931.06312), Time.at(1298858931.10826))
|
70
|
+
et.addEvent("R2", "R2-!render_template.action_view", Time.at(1298858931.10888), Time.at(1298858931.11169))
|
71
|
+
et.addEvent("R3", "R3-render_template.action_view", Time.at(1298858931.06305), Time.at(1298858931.11211))
|
72
|
+
et.addEvent("P", "P-process_action.action_controller", Time.at(1298858931.01187), Time.at(1298858931.11537))
|
73
|
+
|
74
|
+
trace = et.dumpFrames
|
75
|
+
puts "\n"
|
76
|
+
printTree(trace.root, 1)
|
77
|
+
expected={"DEFAULT" => {"S" => {}, "P" => { "R3" => {"R1" => {"SQL" => {}}, "R2" => {}}}}}
|
78
|
+
isCorrect(trace.root, expected)
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
require 'stringio'
|
3
|
+
require 'test/unit'
|
4
|
+
require File.dirname(__FILE__) + '/../lib/agent'
|
5
|
+
|
6
|
+
class TestFrameBuilder < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_builder
|
12
|
+
op = Operation.new({}, "fakeSubMethod", "PunyOperation")
|
13
|
+
fb = FrameBuilder.instance
|
14
|
+
t = TraceBuilder.instance.createTrace(
|
15
|
+
fb.createFrame(0, op, 0, 20, [ fb.createFrame(1, op, 0, 10), fb.createFrame(2, op, 10, 20) ]))
|
16
|
+
traceJSON = TraceDispatcher.convert_to_json(t)
|
17
|
+
end
|
18
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
require 'stringio'
|
3
|
+
require 'test/unit'
|
4
|
+
require File.dirname(__FILE__) + '/../lib/agent'
|
5
|
+
|
6
|
+
class TestClass
|
7
|
+
|
8
|
+
def initialize()
|
9
|
+
@noargcalls = 0
|
10
|
+
@argscalls = 0
|
11
|
+
@yields = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def noarg()
|
15
|
+
@noargcalls += 1
|
16
|
+
end
|
17
|
+
|
18
|
+
def argsmethod(myarg)
|
19
|
+
@argscalls += 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def blockmethod()
|
23
|
+
@yields += 1
|
24
|
+
yield
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_accessor :noargcalls, :argscalls, :yields
|
28
|
+
end
|
29
|
+
|
30
|
+
class TestWrapMethod < Test::Unit::TestCase
|
31
|
+
@enters = nil
|
32
|
+
@exits = nil
|
33
|
+
@args = nil
|
34
|
+
@blocks = nil
|
35
|
+
@@wrapper = WrapMethod.instance
|
36
|
+
def setup
|
37
|
+
@enters = 0
|
38
|
+
@exits = 0
|
39
|
+
@args = nil
|
40
|
+
@blocks = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def wrapit(method)
|
44
|
+
WrapMethod.instance.wrap(TestClass, method) do |point, clazz, method, args|
|
45
|
+
puts point.to_s + ":" + method.to_s
|
46
|
+
if args != nil
|
47
|
+
args.each do |arg|
|
48
|
+
puts "\t" + arg
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if point == :enter
|
53
|
+
@enters += 1
|
54
|
+
@args = args
|
55
|
+
end
|
56
|
+
if point == :exit
|
57
|
+
@exits += 1
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_wrap_no_arg
|
64
|
+
wrapit("noarg")
|
65
|
+
go = TestClass.new
|
66
|
+
go.insight_wrapped_noarg
|
67
|
+
assert go.noargcalls == 1
|
68
|
+
assert @enters == 0
|
69
|
+
assert @exits == 0
|
70
|
+
assert @args == nil
|
71
|
+
go.noarg
|
72
|
+
assert go.noargcalls == 2
|
73
|
+
assert @enters == 1
|
74
|
+
assert @exits == 1
|
75
|
+
assert @args == nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_wrap_argsmethod
|
79
|
+
wrapit("argsmethod")
|
80
|
+
go = TestClass.new
|
81
|
+
go.insight_wrapped_argsmethod "Hi There"
|
82
|
+
assert go.argscalls == 1
|
83
|
+
assert @enters == 0
|
84
|
+
assert @exits == 0
|
85
|
+
assert @args == nil
|
86
|
+
go.argsmethod "Oats"
|
87
|
+
assert go.argscalls == 2
|
88
|
+
assert @enters == 1
|
89
|
+
assert @exits == 1
|
90
|
+
assert @args == "Oats"
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_wrap_blockmethod
|
94
|
+
wrapit("blockmethod")
|
95
|
+
inblocks = 0
|
96
|
+
go = TestClass.new
|
97
|
+
go.insight_wrapped_blockmethod { puts "In Block1"; inblocks += 1 }
|
98
|
+
assert go.yields == 1
|
99
|
+
assert @enters == 0
|
100
|
+
assert @exits == 0
|
101
|
+
assert @args == nil
|
102
|
+
assert inblocks == 1
|
103
|
+
go.blockmethod { puts "In Block2"; inblocks += 1 }
|
104
|
+
assert go.yields == 2
|
105
|
+
assert @enters == 1
|
106
|
+
assert @exits == 1
|
107
|
+
assert @args == nil
|
108
|
+
assert inblocks == 2
|
109
|
+
end
|
110
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: insight_agent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.7
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- John V Kew
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-06-19 00:00:00.000000000 +03:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
requirement: &22063820 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 3.0.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *22063820
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: hoe
|
28
|
+
requirement: &22063120 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.9.4
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *22063120
|
37
|
+
description: This agent will report traces to Insight
|
38
|
+
email:
|
39
|
+
- jkew@vmware.com
|
40
|
+
executables: []
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files:
|
43
|
+
- History.txt
|
44
|
+
- Manifest.txt
|
45
|
+
- PostInstall.txt
|
46
|
+
files:
|
47
|
+
- History.txt
|
48
|
+
- Manifest.txt
|
49
|
+
- PostInstall.txt
|
50
|
+
- README.rdoc
|
51
|
+
- Rakefile
|
52
|
+
- lib/agent.rb
|
53
|
+
- lib/agent/frame.rb
|
54
|
+
- lib/agent/frame_builder.rb
|
55
|
+
- lib/agent/operation.rb
|
56
|
+
- lib/agent/trace.rb
|
57
|
+
- lib/agent/trace_builder.rb
|
58
|
+
- lib/agent/trace_dispatcher.rb
|
59
|
+
- lib/agent/trace_queue.rb
|
60
|
+
- lib/agent/insight_agent.rb
|
61
|
+
- lib/agent/event_tree.rb
|
62
|
+
- lib/agent/wrap_method.rb
|
63
|
+
- lib/agent/method_operation.rb
|
64
|
+
- script/console
|
65
|
+
- script/destroy
|
66
|
+
- script/generate
|
67
|
+
- test/test_agent.rb
|
68
|
+
- test/test_helper.rb
|
69
|
+
- test/test_wrap_method.rb
|
70
|
+
- test/test_event_tree.rb
|
71
|
+
- test/test_frame_builder.rb
|
72
|
+
- .gemtest
|
73
|
+
has_rdoc: true
|
74
|
+
homepage:
|
75
|
+
licenses: []
|
76
|
+
post_install_message: PostInstall.txt
|
77
|
+
rdoc_options:
|
78
|
+
- --main
|
79
|
+
- README.txt
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project: insight_agent
|
96
|
+
rubygems_version: 1.6.2
|
97
|
+
signing_key:
|
98
|
+
specification_version: 3
|
99
|
+
summary: An agent for Insight
|
100
|
+
test_files:
|
101
|
+
- test/test_agent.rb
|
102
|
+
- test/test_wrap_method.rb
|
103
|
+
- test/test_helper.rb
|
104
|
+
- test/test_event_tree.rb
|
105
|
+
- test/test_frame_builder.rb
|