deja-vu 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Change.log +4 -0
- data/LICENSE +19 -0
- data/Makefile +0 -0
- data/README +22 -0
- data/Rakefile +31 -0
- data/example-playback/README +6 -0
- data/example-playback/analysis.rb +7 -0
- data/example-playback/playback.rb +7 -0
- data/example-playback/repo/recordings/DejaVuNS/DejaVu/recordings +1 -0
- data/example-playback/repo/recordings/DejaVuNS/Recording/bah7aa==/n--ab7689c618445bb17b626741588b50c0500c8f79 +1 -0
- data/example/Makefile +6 -0
- data/example/config.ru +9 -0
- data/example/deja-vu-recordings/6b44093baa40d257106bae6dbcda53af9a57e61f.session +53 -0
- data/example/deja-vu-recordings/BAh7AA==/n--ab7689c618445bb17b626741588b50c0500c8f79.session +598 -0
- data/example/deja-vu-recordings/README +1 -0
- data/example/myapp.rb +15 -0
- data/example/repo/recordings/DejaVuNS/DejaVu/recordings +1 -0
- data/example/repo/recordings/DejaVuNS/Recording/bah7aa==/n--ab7689c618445bb17b626741588b50c0500c8f79 +1 -0
- data/example/views/index.haml +0 -0
- data/example/views/layout.haml +0 -0
- data/lib/deja-vu.rb +72 -0
- data/lib/deja-vu/analyzer.rb +133 -0
- data/lib/deja-vu/init.rb +3 -0
- data/lib/deja-vu/model/dejavu.rb +42 -0
- data/lib/deja-vu/model/generated_model/DejaVuNS.rb +1536 -0
- data/lib/deja-vu/model/init.rb +3 -0
- data/lib/deja-vu/model/record.rb +58 -0
- data/lib/deja-vu/model/recording.rb +55 -0
- data/lib/deja-vu/model/xampl-gen.rb +39 -0
- data/lib/deja-vu/model/xml/dejavu.xml +28 -0
- data/lib/deja-vu/player.rb +241 -0
- data/lib/deja-vu/recorder.rb +93 -0
- data/lib/rack-session-listener.rb +29 -0
- metadata +94 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'patron'
|
2
|
+
|
3
|
+
module DejaVuNS
|
4
|
+
class Record
|
5
|
+
=begin
|
6
|
+
<recording pid=""
|
7
|
+
cookie=""
|
8
|
+
stamp=""
|
9
|
+
agent="">
|
10
|
+
|
11
|
+
<record id=""
|
12
|
+
stamp=""
|
13
|
+
status=""
|
14
|
+
httpmethod=""
|
15
|
+
url=""
|
16
|
+
request-time="">
|
17
|
+
=end
|
18
|
+
|
19
|
+
# def initialize(pid)
|
20
|
+
# super
|
21
|
+
# assign_agent('Deja-Vu/1.0')
|
22
|
+
# end
|
23
|
+
|
24
|
+
def assign_agent(agent)
|
25
|
+
unless defined? @sess
|
26
|
+
@sess = Patron::Session.new
|
27
|
+
@sess.timeout = 10
|
28
|
+
@sess.headers['User-Agent'] = agent
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute_as(agent = nil)
|
33
|
+
assign_agent(agent) if agent
|
34
|
+
|
35
|
+
resp = nil
|
36
|
+
case self.httpmethod
|
37
|
+
when 'GET'
|
38
|
+
resp = @sess.get(self.url)
|
39
|
+
when 'POST'
|
40
|
+
resp = @sess.post(self.url)
|
41
|
+
when 'PUT'
|
42
|
+
resp = @sess.put(self.url)
|
43
|
+
when 'DELETE'
|
44
|
+
resp = @sess.delete(self.url)
|
45
|
+
end
|
46
|
+
|
47
|
+
return resp
|
48
|
+
|
49
|
+
# if resp.status < 400
|
50
|
+
# puts resp.body
|
51
|
+
# end
|
52
|
+
# sess.put("/foo/baz", "some data")
|
53
|
+
# sess.delete("/foo/baz")
|
54
|
+
# sess.post("/foo/stuff", "some data", {"Content-Type" => "text/plain"})
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
=begin
|
2
|
+
<recording pid=""
|
3
|
+
cookie=""
|
4
|
+
stamp=""
|
5
|
+
agent="">
|
6
|
+
|
7
|
+
<record id=""
|
8
|
+
stamp=""
|
9
|
+
status=""
|
10
|
+
method=""
|
11
|
+
url=""
|
12
|
+
request-time="">
|
13
|
+
|
14
|
+
<header name="" value=""/>
|
15
|
+
|
16
|
+
<body><![CDATA[HTML OR WHATEVER HERE]]></body>
|
17
|
+
|
18
|
+
<param name="" value=""/>
|
19
|
+
|
20
|
+
<multipart-reference name="" file-path=""/>
|
21
|
+
|
22
|
+
</record>
|
23
|
+
|
24
|
+
</recording>
|
25
|
+
=end
|
26
|
+
module DejaVuNS
|
27
|
+
class Recording
|
28
|
+
|
29
|
+
def self.find_by_identifier(ident)
|
30
|
+
DejaVuNS.root.recording.each do |rec|
|
31
|
+
return rec if rec.cookie == ident
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.find_by_pid(pid)
|
37
|
+
rec = nil
|
38
|
+
DejaVuNS.transaction do
|
39
|
+
rec = DejaVuNS.root.recording[pid]
|
40
|
+
end
|
41
|
+
rec
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.all_recordings
|
45
|
+
recordings = []
|
46
|
+
DejaVuNS.transaction do
|
47
|
+
DejaVuNS.root.recording.each do |rec|
|
48
|
+
recordings << rec
|
49
|
+
end
|
50
|
+
end
|
51
|
+
recordings
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby -w -I..
|
2
|
+
|
3
|
+
if $0 == __FILE__ then
|
4
|
+
|
5
|
+
class File
|
6
|
+
def File.sjoin(*args)
|
7
|
+
File.join(args.select{ | o | o })
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'xampl-generator'
|
12
|
+
|
13
|
+
include XamplGenerator
|
14
|
+
include Xampl
|
15
|
+
|
16
|
+
Xampl.transaction("setup", :in_memory) do
|
17
|
+
directory = File.sjoin(".", "generated_model")
|
18
|
+
|
19
|
+
the_options = Xampl.make(Options) { | options |
|
20
|
+
options.new_index_attribute("pid").persisted = true
|
21
|
+
options.new_index_attribute("id")
|
22
|
+
options.resolve("http://soldierofcode.com/deja-vu", "DejaVuNS", 'dejavu')
|
23
|
+
}
|
24
|
+
|
25
|
+
filenames = Dir.glob("./xml/**/*.xml")
|
26
|
+
|
27
|
+
generator = Generator.new
|
28
|
+
generator.go(:options => the_options,
|
29
|
+
:filenames => filenames,
|
30
|
+
:directory => directory)
|
31
|
+
|
32
|
+
#puts generator.print_elements("./generated-elements.xml")
|
33
|
+
|
34
|
+
$LOAD_PATH.unshift(directory)
|
35
|
+
|
36
|
+
exit!
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<deja-vu xmlns="http://soldierofcode.com/deja-vu" pid="">
|
2
|
+
|
3
|
+
<recording pid=""
|
4
|
+
cookie=""
|
5
|
+
stamp=""
|
6
|
+
agent=""
|
7
|
+
ip="">
|
8
|
+
|
9
|
+
<record id=""
|
10
|
+
stamp=""
|
11
|
+
status=""
|
12
|
+
httpmethod=""
|
13
|
+
url=""
|
14
|
+
request-time="">
|
15
|
+
|
16
|
+
<header name="" value=""/>
|
17
|
+
|
18
|
+
<body><![CDATA[HTML OR WHATEVER HERE]]></body>
|
19
|
+
|
20
|
+
<param name="" value=""/>
|
21
|
+
|
22
|
+
<multipart-reference name="" file-path=""/>
|
23
|
+
|
24
|
+
</record>
|
25
|
+
|
26
|
+
</recording>
|
27
|
+
|
28
|
+
</deja-vu>
|
@@ -0,0 +1,241 @@
|
|
1
|
+
require ~'model/init'
|
2
|
+
require 'patron'
|
3
|
+
|
4
|
+
module SoldierOfCode
|
5
|
+
module DejaVu
|
6
|
+
|
7
|
+
#
|
8
|
+
# Should do the following:
|
9
|
+
# - be configurable in
|
10
|
+
# - ignore/respect time between requests
|
11
|
+
# - ignore/call specific mime type calls (like favicon.ico etc) - css,js etc too
|
12
|
+
# - use webrat, curl, wget ...
|
13
|
+
# - turn on step through (interactive control of when requests are made)
|
14
|
+
# - display server log on each step or not
|
15
|
+
#
|
16
|
+
class Player
|
17
|
+
|
18
|
+
attr_accessor :respect_timings, :mime_ignore, :mime_watch, :use_client, :step_through, :display_log
|
19
|
+
|
20
|
+
def initialize(recording_pid=nil, opts={:respect_timings=>'yes'})
|
21
|
+
|
22
|
+
# @recording = DejaVuNS::Recording.find_by_pid(recording_pid)
|
23
|
+
# @recordings = DejaVuNS::Recording.find_by_pid(recording_pid)
|
24
|
+
@recordings = []
|
25
|
+
unless recording_pid
|
26
|
+
# anlyize all the recordings as a mass
|
27
|
+
DejaVuNS::Recording.all_recordings.each do |rec|
|
28
|
+
@recordings << rec
|
29
|
+
end
|
30
|
+
else
|
31
|
+
# only analyize overview of the provided pid
|
32
|
+
@recordings << DejaVuNS::Recording.find_by_pid(recording_pid)
|
33
|
+
end
|
34
|
+
|
35
|
+
@start_time = opts[:start_time]
|
36
|
+
@end_time = opts[:end_time]
|
37
|
+
@respect_timings = ('yes' == opts[:respect_timings])
|
38
|
+
|
39
|
+
# @mime_ignore = opts[:mime_ignore] # => is an array of mime types to ignore
|
40
|
+
# @mime_watch = opts[:mime_watch] # => is an array of mime types to exeucte
|
41
|
+
# @use_client = opts[:use_client] # => what client interface to use
|
42
|
+
|
43
|
+
# @step_through = ('yes'==opts[:step_through])
|
44
|
+
# @display_log = ('yes'==opts[:display_log])
|
45
|
+
|
46
|
+
@current_step = 0
|
47
|
+
|
48
|
+
@frames = []
|
49
|
+
build_frames
|
50
|
+
end
|
51
|
+
|
52
|
+
def build_frames
|
53
|
+
@recordings.each do |rec|
|
54
|
+
rec.record.each do |record|
|
55
|
+
if @start_time && !@end_time && record.stamp.to_i >= @start_time # only a start stamp is provided so add all upto this date
|
56
|
+
@frames << RequestFrame.new(rec,record)
|
57
|
+
elsif @start_time && @end_time && record.stamp.to_i >= @start_time && record.stamp.to_i <= @end_time # start and end stamps provided (envelope of time)
|
58
|
+
@frames << RequestFrame.new(rec,record)
|
59
|
+
elsif !@start_time && @end_time && record.stamp.to_i <= @end_time # Just an end time provided - everthing upto this time stamp
|
60
|
+
@frames << RequestFrame.new(rec,record)
|
61
|
+
elsif !@start_time && !@end_time
|
62
|
+
@frames << RequestFrame.new(rec,record)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def play
|
70
|
+
# plays all frames
|
71
|
+
resps = []
|
72
|
+
last_time_stamp = @frames.first.stamp.to_i
|
73
|
+
@frames.each do |frame|
|
74
|
+
|
75
|
+
resps << frame.invoke
|
76
|
+
last_time_delta = frame.stamp.to_i - last_time_stamp
|
77
|
+
last_time_stamp = frame.stamp.to_i
|
78
|
+
if @respect_timings then
|
79
|
+
puts "#{__FILE__}:#{__LINE__} #{__method__} LAST DELTA: #{last_time_delta} --- CURRENT: #{Time.new.to_i}"
|
80
|
+
# local_delta = last_time_delta / 1000.0
|
81
|
+
# if local_delta > 1
|
82
|
+
# sleep(local_delta)
|
83
|
+
# end
|
84
|
+
sleep(last_time_delta)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
puts "#{__FILE__}:#{__LINE__} #{__method__} RESPS: #{resps.inspect}"
|
88
|
+
resps
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
def play_frame
|
93
|
+
return nil unless @frames
|
94
|
+
|
95
|
+
return if @current_step > @frames.size
|
96
|
+
|
97
|
+
frame = @frames[@current_step]
|
98
|
+
|
99
|
+
resp = frame.invoke
|
100
|
+
|
101
|
+
@current_step += 1
|
102
|
+
|
103
|
+
resp
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
def fast_forward(x=1)
|
108
|
+
@current_step += x
|
109
|
+
end
|
110
|
+
|
111
|
+
def rewind(x=1)
|
112
|
+
@current_step -= x
|
113
|
+
@current_step = 0 if @current_step < 0
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class RequestFrame
|
118
|
+
attr_accessor :url, :response, :status_code, :record, :agent
|
119
|
+
|
120
|
+
def initialize(recording, record)
|
121
|
+
assign_agent(recording.agent)
|
122
|
+
@record = record
|
123
|
+
@url = record.url
|
124
|
+
@status_code = 0
|
125
|
+
@response = nil
|
126
|
+
end
|
127
|
+
|
128
|
+
def invoke
|
129
|
+
resp = execute_as
|
130
|
+
@status_code = @response.status
|
131
|
+
resp
|
132
|
+
end
|
133
|
+
|
134
|
+
def stamp
|
135
|
+
@record.stamp
|
136
|
+
end
|
137
|
+
|
138
|
+
def httpmethod
|
139
|
+
@record.httpmethod
|
140
|
+
end
|
141
|
+
|
142
|
+
def request_time
|
143
|
+
@record.request_time
|
144
|
+
end
|
145
|
+
|
146
|
+
def assign_agent(agent)
|
147
|
+
unless defined? @sess
|
148
|
+
@sess = Patron::Session.new
|
149
|
+
@sess.timeout = 10
|
150
|
+
@sess.headers['User-Agent'] = agent
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def execute_as(agent = nil)
|
155
|
+
assign_agent(agent) if agent
|
156
|
+
|
157
|
+
@response = nil
|
158
|
+
case self.httpmethod
|
159
|
+
when 'GET'
|
160
|
+
@response = @sess.get(self.url)
|
161
|
+
when 'POST'
|
162
|
+
@response = @sess.post(self.url)
|
163
|
+
when 'PUT'
|
164
|
+
@response = @sess.put(self.url)
|
165
|
+
when 'DELETE'
|
166
|
+
@response = @sess.delete(self.url)
|
167
|
+
end
|
168
|
+
|
169
|
+
return @response
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
=begin
|
177
|
+
def initialize
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
def overview(specific_recording_pid=nil, start_stamp=nil, end_stamp=nil)
|
183
|
+
|
184
|
+
recordings = []
|
185
|
+
unless specific_recording_pid
|
186
|
+
# anlyize all the recordings as a mass
|
187
|
+
DejaVuNS::Recording.all_recordings.each do |rec|
|
188
|
+
recordings << rec
|
189
|
+
end
|
190
|
+
else
|
191
|
+
# only analyize overview of the provided pid
|
192
|
+
recordings << DejaVuNS::Recording.find_by_pid(specific_recording_pid)
|
193
|
+
end
|
194
|
+
|
195
|
+
perform_overview(recordings, start_stamp, end_stamp)
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
def perform_overview(recordings, start_stamp=nil, end_stamp=nil)
|
200
|
+
|
201
|
+
analysis = Analysis.new
|
202
|
+
|
203
|
+
analysis.analysis_title = "Single User: #{recordings.first.ip}"
|
204
|
+
|
205
|
+
records = []
|
206
|
+
recordings.each do |rec|
|
207
|
+
|
208
|
+
analysis.add_user
|
209
|
+
|
210
|
+
rec.record.each do |record|
|
211
|
+
if start_stamp && !end_stamp && record.stamp.to_i >= start_stamp # only a start stamp is provided so add all upto this date
|
212
|
+
records << record
|
213
|
+
elsif start_stamp && end_stamp && record.stamp.to_i >= start_stamp && record.stamp.to_i <= end_stamp # start and end stamps provided (envelope of time)
|
214
|
+
records << record
|
215
|
+
elsif !start_stamp && end_stamp && record.stamp.to_i <= end_stamp # Just an end time provided - everthing upto this time stamp
|
216
|
+
records << record
|
217
|
+
elsif !start_stamp && !end_stamp
|
218
|
+
records << record
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# these are vetted against the stamps
|
224
|
+
urls_seen = []
|
225
|
+
records.each do |record|
|
226
|
+
|
227
|
+
analysis.add_request(record.request_time.to_f)
|
228
|
+
|
229
|
+
unless urls_seen.include?(record.url)
|
230
|
+
analysis.add_unique
|
231
|
+
urls_seen << record.url
|
232
|
+
end
|
233
|
+
|
234
|
+
analysis.add_error if record.status.to_i >= 400
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
analysis
|
239
|
+
end
|
240
|
+
|
241
|
+
=end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require ~'model/init'
|
2
|
+
|
3
|
+
module SoldierOfCode
|
4
|
+
module DejaVu
|
5
|
+
class Recorder
|
6
|
+
|
7
|
+
# @@writing = {} unless defined? @@writing
|
8
|
+
#
|
9
|
+
# def self.writing?(file_name)
|
10
|
+
# @@writing[file_name]
|
11
|
+
# end
|
12
|
+
attr_accessor :identifier
|
13
|
+
|
14
|
+
def initialize(opt)
|
15
|
+
@identifier = opt['cookie_name']
|
16
|
+
@opt = opt
|
17
|
+
end
|
18
|
+
|
19
|
+
def export(to_file_named)
|
20
|
+
# TODO -- IMPLEMENT THIS
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
#
|
25
|
+
#
|
26
|
+
def record(env, resp, req, start_time, end_time, identifier)
|
27
|
+
|
28
|
+
# status, headers, body
|
29
|
+
# resp[0], resp[1], resp[2]
|
30
|
+
|
31
|
+
# 1. try to locate the recording
|
32
|
+
recording = nil
|
33
|
+
DejaVuNS.transaction do
|
34
|
+
recording = DejaVuNS::Recording.find_by_identifier(@identifier)
|
35
|
+
|
36
|
+
# puts "#{__FILE__}:#{__LINE__} #{__method__} #{recording.class.name}"
|
37
|
+
# 1.b if not found then create a new one
|
38
|
+
unless recording
|
39
|
+
recording = DejaVuNS.root.new_recording(DejaVuNS.pid_from_string(@identifier||identifier))
|
40
|
+
recording.cookie = req.cookies[@identifier||identifier]
|
41
|
+
recording.stamp = Time.new.to_i
|
42
|
+
recording.agent = env['HTTP_USER_AGENT']
|
43
|
+
recording.ip = env['REMOTE_ADDR']
|
44
|
+
end
|
45
|
+
|
46
|
+
# 2. create a new record
|
47
|
+
# puts "#{__FILE__}:#{__LINE__} #{__method__} #{recording.class.name}"
|
48
|
+
record = recording.new_record("#{Time.new.to_i}")
|
49
|
+
record.stamp = "#{Time.new.to_i}"
|
50
|
+
record.status = "#{resp[0]}"
|
51
|
+
record.httpmethod = env['REQUEST_METHOD']
|
52
|
+
record.url = "#{env['rack.url_scheme']}://#{env['HTTP_HOST']}#{env['REQUEST_URI']}"
|
53
|
+
record.request_time = ("#{end_time.to_i}.#{end_time.usec}".to_f - "#{start_time.to_i}.#{start_time.usec}".to_f).to_s
|
54
|
+
|
55
|
+
# 3. add the body etc elements
|
56
|
+
resp[1].each do |k, v|
|
57
|
+
h = record.new_header
|
58
|
+
h.name = k
|
59
|
+
h.value = v
|
60
|
+
end
|
61
|
+
|
62
|
+
record.new_body().content = "<![CDATA[#{resp[2]}]]>"
|
63
|
+
|
64
|
+
if req.post? && env['CONTENT_TYPE'] =~ %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
|
65
|
+
# its multipart
|
66
|
+
# <multipart-reference name="" file-path=""/>
|
67
|
+
# can I grab it off the env object?
|
68
|
+
puts "#{__FILE__}:#{__LINE__} #{__method__} NOT IMPLEMENTED - MULTIPART"
|
69
|
+
else
|
70
|
+
# safe param recording
|
71
|
+
req.params.each do |k,v|
|
72
|
+
p = record.new_param()
|
73
|
+
p.name = k
|
74
|
+
p.value = v
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
def identifier_change(new_identifier)
|
83
|
+
DejaVuNS.transaction do
|
84
|
+
recording = DejaVuNS::Recording.find_by_identifier(@identifier)
|
85
|
+
if recording
|
86
|
+
@identifier = new_identifier
|
87
|
+
recording.cookie = new_identifier
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|