rack-mini-profiler 0.1.24 → 0.1.29
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.
Potentially problematic release.
This version of rack-mini-profiler might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Ruby/CHANGELOG +25 -0
- data/Ruby/README.md +11 -0
- data/Ruby/lib/html/flamegraph.html +351 -0
- data/Ruby/lib/html/includes.js +82 -67
- data/Ruby/lib/mini_profiler/config.rb +10 -9
- data/Ruby/lib/mini_profiler/flame_graph.rb +4 -4
- data/Ruby/lib/mini_profiler/profiler.rb +75 -40
- data/Ruby/lib/mini_profiler/sql_timer_struct.rb +1 -1
- data/Ruby/lib/mini_profiler/storage/abstract_store.rb +4 -3
- data/Ruby/lib/mini_profiler/storage/redis_store.rb +3 -3
- data/Ruby/lib/mini_profiler/version.rb +1 -1
- data/Ruby/lib/mini_profiler_rails/railtie.rb +44 -40
- data/Ruby/lib/patches/sql_patches.rb +6 -1
- data/rack-mini-profiler.gemspec +2 -1
- metadata +6 -4
@@ -7,15 +7,15 @@ module Rack
|
|
7
7
|
@attributes.concat vars
|
8
8
|
super(*vars)
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def self.attributes
|
12
12
|
@attributes
|
13
13
|
end
|
14
14
|
|
15
15
|
attr_accessor :auto_inject, :base_url_path, :pre_authorize_cb, :position,
|
16
|
-
:backtrace_remove, :backtrace_includes, :backtrace_ignores, :skip_schema_queries,
|
16
|
+
:backtrace_remove, :backtrace_includes, :backtrace_ignores, :skip_schema_queries,
|
17
17
|
:storage, :user_provider, :storage_instance, :storage_options, :skip_paths, :authorization_mode,
|
18
|
-
:toggle_shortcut, :start_hidden
|
18
|
+
:toggle_shortcut, :start_hidden, :backtrace_threshold_ms
|
19
19
|
|
20
20
|
# Deprecated options
|
21
21
|
attr_accessor :use_existing_jquery
|
@@ -24,10 +24,10 @@ module Rack
|
|
24
24
|
new.instance_eval {
|
25
25
|
@auto_inject = true # automatically inject on every html page
|
26
26
|
@base_url_path = "/mini-profiler-resources/"
|
27
|
-
|
27
|
+
|
28
28
|
# called prior to rack chain, to ensure we are allowed to profile
|
29
|
-
@pre_authorize_cb = lambda {|env| true}
|
30
|
-
|
29
|
+
@pre_authorize_cb = lambda {|env| true}
|
30
|
+
|
31
31
|
# called after rack chain, to ensure we are REALLY allowed to profile
|
32
32
|
@position = 'left' # Where it is displayed
|
33
33
|
@skip_schema_queries = false
|
@@ -36,16 +36,17 @@ module Rack
|
|
36
36
|
@authorization_mode = :allow_all
|
37
37
|
@toggle_shortcut = 'Alt+P'
|
38
38
|
@start_hidden = false
|
39
|
+
@backtrace_threshold_ms = 0
|
39
40
|
self
|
40
41
|
}
|
41
42
|
end
|
42
43
|
|
43
44
|
def merge!(config)
|
44
45
|
return unless config
|
45
|
-
if Hash === config
|
46
|
+
if Hash === config
|
46
47
|
config.each{|k,v| instance_variable_set "@#{k}",v}
|
47
|
-
else
|
48
|
-
self.class.attributes.each{ |k|
|
48
|
+
else
|
49
|
+
self.class.attributes.each{ |k|
|
49
50
|
v = config.send k
|
50
51
|
instance_variable_set "@#{k}", v if v
|
51
52
|
}
|
@@ -6,9 +6,9 @@ class Rack::MiniProfiler::FlameGraph
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def graph_data
|
9
|
-
height = 0
|
9
|
+
height = 0
|
10
10
|
|
11
|
-
table = []
|
11
|
+
table = []
|
12
12
|
prev = []
|
13
13
|
|
14
14
|
# a 2d array makes collapsing easy
|
@@ -17,7 +17,7 @@ class Rack::MiniProfiler::FlameGraph
|
|
17
17
|
|
18
18
|
stack.reverse.map{|r| r.to_s}.each_with_index do |frame, i|
|
19
19
|
|
20
|
-
if !prev[i].nil?
|
20
|
+
if !prev[i].nil?
|
21
21
|
last_col = prev[i]
|
22
22
|
if last_col[0] == frame
|
23
23
|
last_col[1] += 1
|
@@ -26,7 +26,7 @@ class Rack::MiniProfiler::FlameGraph
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
prev[i] = [frame, 1]
|
29
|
+
prev[i] = [frame, 1]
|
30
30
|
col << prev[i]
|
31
31
|
end
|
32
32
|
prev = prev[0..col.length-1].to_a
|
@@ -123,7 +123,7 @@ module Rack
|
|
123
123
|
|
124
124
|
# Otherwise give the HTML back
|
125
125
|
html = MiniProfiler.share_template.dup
|
126
|
-
html.gsub!(/\{path\}/, @config.base_url_path)
|
126
|
+
html.gsub!(/\{path\}/, "#{env['SCRIPT_NAME']}#{@config.base_url_path}")
|
127
127
|
html.gsub!(/\{version\}/, MiniProfiler::VERSION)
|
128
128
|
html.gsub!(/\{json\}/, result_json)
|
129
129
|
html.gsub!(/\{includes\}/, get_profile_script(env))
|
@@ -224,6 +224,7 @@ module Rack
|
|
224
224
|
|
225
225
|
MiniProfiler.create_current(env, @config)
|
226
226
|
MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
|
227
|
+
|
227
228
|
if query_string =~ /pp=normal-backtrace/
|
228
229
|
client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT
|
229
230
|
elsif query_string =~ /pp=no-backtrace/
|
@@ -239,7 +240,7 @@ module Rack
|
|
239
240
|
done_sampling = false
|
240
241
|
quit_sampler = false
|
241
242
|
backtraces = nil
|
242
|
-
|
243
|
+
|
243
244
|
if query_string =~ /pp=sample/ || query_string =~ /pp=flamegraph/
|
244
245
|
current.measure = false
|
245
246
|
skip_frames = 0
|
@@ -255,7 +256,7 @@ module Rack
|
|
255
256
|
break if done_sampling
|
256
257
|
i -= 1
|
257
258
|
backtraces << (has_backtrace_locations ? t.backtrace_locations : t.backtrace)
|
258
|
-
|
259
|
+
|
259
260
|
# On my machine using Ruby 2.0 this give me excellent fidelity of stack trace per 1.2ms
|
260
261
|
# with this fidelity analysis becomes very powerful
|
261
262
|
sleep 0.0005
|
@@ -266,18 +267,31 @@ module Rack
|
|
266
267
|
}
|
267
268
|
end
|
268
269
|
|
269
|
-
|
270
|
+
trace_exceptions = query_string =~ /pp=trace-exceptions/ && defined? TracePoint
|
271
|
+
status, headers, body, exceptions,trace = nil
|
272
|
+
|
270
273
|
start = Time.now
|
274
|
+
|
275
|
+
if trace_exceptions
|
276
|
+
exceptions = []
|
277
|
+
trace = TracePoint.new(:raise) do |tp|
|
278
|
+
exceptions << tp.raised_exception
|
279
|
+
end
|
280
|
+
trace.enable
|
281
|
+
end
|
282
|
+
|
271
283
|
begin
|
272
284
|
|
273
285
|
# Strip all the caching headers so we don't get 304s back
|
274
286
|
# This solves a very annoying bug where rack mini profiler never shows up
|
275
|
-
env['HTTP_IF_MODIFIED_SINCE'] =
|
276
|
-
env['HTTP_IF_NONE_MATCH'] =
|
287
|
+
env['HTTP_IF_MODIFIED_SINCE'] = ''
|
288
|
+
env['HTTP_IF_NONE_MATCH'] = ''
|
277
289
|
|
278
290
|
status,headers,body = @app.call(env)
|
279
291
|
client_settings.write!(headers)
|
280
292
|
ensure
|
293
|
+
trace.disable if trace
|
294
|
+
|
281
295
|
if backtraces
|
282
296
|
done_sampling = true
|
283
297
|
sleep 0.001 until quit_sampler
|
@@ -298,6 +312,11 @@ module Rack
|
|
298
312
|
return [status,headers,body] if skip_it
|
299
313
|
|
300
314
|
# we must do this here, otherwise current[:discard] is not being properly treated
|
315
|
+
if trace_exceptions
|
316
|
+
body.close if body.respond_to? :close
|
317
|
+
return dump_exceptions exceptions
|
318
|
+
end
|
319
|
+
|
301
320
|
if query_string =~ /pp=env/
|
302
321
|
body.close if body.respond_to? :close
|
303
322
|
return dump_env env
|
@@ -316,7 +335,7 @@ module Rack
|
|
316
335
|
body.close if body.respond_to? :close
|
317
336
|
if query_string =~ /pp=sample/
|
318
337
|
return analyze(backtraces, page_struct)
|
319
|
-
else
|
338
|
+
else
|
320
339
|
return flame_graph(backtraces, page_struct)
|
321
340
|
end
|
322
341
|
end
|
@@ -327,46 +346,51 @@ module Rack
|
|
327
346
|
@storage.save(page_struct)
|
328
347
|
|
329
348
|
# inject headers, script
|
330
|
-
if status == 200
|
331
|
-
|
349
|
+
if headers['Content-Type'] && status == 200
|
332
350
|
client_settings.write!(headers)
|
333
351
|
|
334
|
-
|
335
|
-
|
336
|
-
headers.delete('ETag')
|
337
|
-
headers.delete('Date')
|
338
|
-
headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
|
339
|
-
|
340
|
-
# inject header
|
341
|
-
if headers.is_a? Hash
|
342
|
-
headers['X-MiniProfiler-Ids'] = ids_json(env)
|
343
|
-
end
|
344
|
-
|
345
|
-
# inject script
|
346
|
-
if current.inject_js \
|
347
|
-
&& headers.has_key?('Content-Type') \
|
348
|
-
&& !headers['Content-Type'].match(/text\/html/).nil? then
|
349
|
-
|
350
|
-
response = Rack::Response.new([], status, headers)
|
351
|
-
script = self.get_profile_script(env)
|
352
|
-
|
353
|
-
if String === body
|
354
|
-
response.write inject(body,script)
|
355
|
-
else
|
356
|
-
body.each { |fragment| response.write inject(fragment, script) }
|
357
|
-
end
|
358
|
-
body.close if body.respond_to? :close
|
359
|
-
return response.finish
|
360
|
-
end
|
352
|
+
result = inject_profiler(env,status,headers,body)
|
353
|
+
return result if result
|
361
354
|
end
|
362
355
|
|
363
356
|
client_settings.write!(headers)
|
364
357
|
[status, headers, body]
|
358
|
+
|
365
359
|
ensure
|
366
360
|
# Make sure this always happens
|
367
361
|
current = nil
|
368
362
|
end
|
369
363
|
|
364
|
+
def inject_profiler(env,status,headers,body)
|
365
|
+
# mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
|
366
|
+
# Rack::ETag has already inserted some nonesense in the chain
|
367
|
+
content_type = headers['Content-Type']
|
368
|
+
|
369
|
+
headers.delete('ETag')
|
370
|
+
headers.delete('Date')
|
371
|
+
headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
|
372
|
+
|
373
|
+
# inject header
|
374
|
+
if headers.is_a? Hash
|
375
|
+
headers['X-MiniProfiler-Ids'] = ids_json(env)
|
376
|
+
end
|
377
|
+
|
378
|
+
if current.inject_js && content_type =~ /text\/html/
|
379
|
+
response = Rack::Response.new([], status, headers)
|
380
|
+
script = self.get_profile_script(env)
|
381
|
+
|
382
|
+
if String === body
|
383
|
+
response.write inject(body,script)
|
384
|
+
else
|
385
|
+
body.each { |fragment| response.write inject(fragment, script) }
|
386
|
+
end
|
387
|
+
body.close if body.respond_to? :close
|
388
|
+
response.finish
|
389
|
+
else
|
390
|
+
nil
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
370
394
|
def inject(fragment, script)
|
371
395
|
if fragment.match(/<\/body>/i)
|
372
396
|
# explicit </body>
|
@@ -388,9 +412,9 @@ module Rack
|
|
388
412
|
index = 1
|
389
413
|
fragment.gsub(regex) do
|
390
414
|
# though malformed there is an edge case where /body exists earlier in the html, work around
|
391
|
-
if index < matches
|
415
|
+
if index < matches
|
392
416
|
index += 1
|
393
|
-
close_tag
|
417
|
+
close_tag
|
394
418
|
else
|
395
419
|
|
396
420
|
# if for whatever crazy reason we dont get a utf string,
|
@@ -404,6 +428,16 @@ module Rack
|
|
404
428
|
end
|
405
429
|
end
|
406
430
|
|
431
|
+
def dump_exceptions(exceptions)
|
432
|
+
headers = {'Content-Type' => 'text/plain'}
|
433
|
+
body = "Exceptions (#{exceptions.length} raised during request)\n\n"
|
434
|
+
exceptions.each do |e|
|
435
|
+
body << "#{e.class} #{e.message}\n#{e.backtrace.join("\n")}\n\n\n\n"
|
436
|
+
end
|
437
|
+
|
438
|
+
[200, headers, [body]]
|
439
|
+
end
|
440
|
+
|
407
441
|
def dump_env(env)
|
408
442
|
headers = {'Content-Type' => 'text/plain'}
|
409
443
|
body = "Rack Environment\n---------------\n"
|
@@ -440,6 +474,7 @@ module Rack
|
|
440
474
|
pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
|
441
475
|
pp=profile-gc-time: perform built-in gc profiling on this request (ruby 1.9.3 only)
|
442
476
|
pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity.
|
477
|
+
pp=trace-exceptions: requires Ruby 2.0, will return all the spots where your application raises execptions
|
443
478
|
"
|
444
479
|
|
445
480
|
client_settings.write!(headers)
|
@@ -451,7 +486,7 @@ module Rack
|
|
451
486
|
data = graph.graph_data
|
452
487
|
|
453
488
|
headers = {'Content-Type' => 'text/html'}
|
454
|
-
|
489
|
+
|
455
490
|
body = IO.read(::File.expand_path('../html/flamegraph.html', ::File.dirname(__FILE__)))
|
456
491
|
body.gsub!("/*DATA*/", ::JSON.generate(data));
|
457
492
|
|
@@ -511,7 +546,7 @@ module Rack
|
|
511
546
|
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
512
547
|
def get_profile_script(env)
|
513
548
|
ids = ids_comma_separated(env)
|
514
|
-
path = @config.base_url_path
|
549
|
+
path = "#{env['SCRIPT_NAME']}#{@config.base_url_path}"
|
515
550
|
version = MiniProfiler::VERSION
|
516
551
|
position = @config.position
|
517
552
|
showTrivial = false
|
@@ -8,7 +8,7 @@ module Rack
|
|
8
8
|
def initialize(query, duration_ms, page, parent, skip_backtrace = false, full_backtrace = false)
|
9
9
|
|
10
10
|
stack_trace = nil
|
11
|
-
unless skip_backtrace
|
11
|
+
unless skip_backtrace || duration_ms < Rack::MiniProfiler.config.backtrace_threshold_ms
|
12
12
|
# Allow us to filter the stack trace
|
13
13
|
stack_trace = ""
|
14
14
|
# Clean up the stack trace if there are options to do so
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Rack
|
2
2
|
class MiniProfiler
|
3
3
|
class AbstractStore
|
4
|
-
|
4
|
+
|
5
5
|
def save(page_struct)
|
6
6
|
raise NotImplementedError.new("save is not implemented")
|
7
7
|
end
|
@@ -23,9 +23,10 @@ module Rack
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def diagnostics(user)
|
26
|
-
|
26
|
+
# this is opt in, no need to explode if not implemented
|
27
|
+
""
|
27
28
|
end
|
28
|
-
|
29
|
+
|
29
30
|
end
|
30
31
|
end
|
31
32
|
end
|
@@ -3,7 +3,7 @@ module Rack
|
|
3
3
|
class RedisStore < AbstractStore
|
4
4
|
|
5
5
|
EXPIRES_IN_SECONDS = 60 * 60 * 24
|
6
|
-
|
6
|
+
|
7
7
|
def initialize(args = nil)
|
8
8
|
@args = args || {}
|
9
9
|
@prefix = @args.delete(:prefix) || 'MPRedisStore'
|
@@ -12,7 +12,7 @@ module Rack
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def save(page_struct)
|
15
|
-
redis.setex "#{@prefix}#{page_struct['Id']}", @expires_in_seconds, Marshal::dump(page_struct)
|
15
|
+
redis.setex "#{@prefix}#{page_struct['Id']}", @expires_in_seconds, Marshal::dump(page_struct)
|
16
16
|
end
|
17
17
|
|
18
18
|
def load(id)
|
@@ -41,7 +41,7 @@ unviewed_ids: #{get_unviewed_ids(user)}
|
|
41
41
|
"
|
42
42
|
end
|
43
43
|
|
44
|
-
private
|
44
|
+
private
|
45
45
|
|
46
46
|
def redis
|
47
47
|
return @redis_connection if @redis_connection
|
@@ -1,61 +1,65 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
|
3
|
-
module MiniProfilerRails
|
3
|
+
module Rack::MiniProfilerRails
|
4
4
|
|
5
|
-
|
5
|
+
# call direct if needed to do a defer init
|
6
|
+
def self.initialize!(app)
|
7
|
+
c = Rack::MiniProfiler.config
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
+
# By default, only show the MiniProfiler in development mode, in production allow profiling if post_authorize_cb is set
|
10
|
+
c.pre_authorize_cb = lambda { |env|
|
11
|
+
!Rails.env.test?
|
12
|
+
}
|
9
13
|
|
10
|
-
|
11
|
-
c.pre_authorize_cb = lambda { |env|
|
12
|
-
!Rails.env.test?
|
13
|
-
}
|
14
|
+
c.skip_paths ||= []
|
14
15
|
|
15
|
-
|
16
|
+
if Rails.env.development?
|
17
|
+
c.skip_paths << "/assets/"
|
18
|
+
c.skip_schema_queries = true
|
19
|
+
end
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
+
if Rails.env.production?
|
22
|
+
c.authorization_mode = :whitelist
|
23
|
+
end
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
+
# The file store is just so much less flaky
|
26
|
+
tmp = Rails.root.to_s + "/tmp/miniprofiler"
|
27
|
+
FileUtils.mkdir_p(tmp) unless File.exists?(tmp)
|
25
28
|
|
26
|
-
|
27
|
-
|
28
|
-
FileUtils.mkdir_p(tmp) unless File.exists?(tmp)
|
29
|
+
c.storage_options = {:path => tmp}
|
30
|
+
c.storage = Rack::MiniProfiler::FileStore
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
+
# Quiet the SQL stack traces
|
33
|
+
c.backtrace_remove = Rails.root.to_s + "/"
|
34
|
+
c.backtrace_includes = [/^\/?(app|config|lib|test)/]
|
35
|
+
c.skip_schema_queries = Rails.env != 'production'
|
32
36
|
|
33
|
-
|
34
|
-
|
35
|
-
c.backtrace_includes = [/^\/?(app|config|lib|test)/]
|
36
|
-
c.skip_schema_queries = Rails.env != 'production'
|
37
|
+
# Install the Middleware
|
38
|
+
app.middleware.insert(0, Rack::MiniProfiler)
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
+
# Attach to various Rails methods
|
41
|
+
::Rack::MiniProfiler.profile_method(ActionController::Base, :process) {|action| "Executing action: #{action}"}
|
42
|
+
::Rack::MiniProfiler.profile_method(ActionView::Template, :render) {|x,y| "Rendering: #{@virtual_path}"}
|
43
|
+
end
|
40
44
|
|
41
|
-
|
42
|
-
::Rack::MiniProfiler.profile_method(ActionController::Base, :process) {|action| "Executing action: #{action}"}
|
43
|
-
::Rack::MiniProfiler.profile_method(ActionView::Template, :render) {|x,y| "Rendering: #{@virtual_path}"}
|
45
|
+
class Railtie < ::Rails::Railtie
|
44
46
|
|
47
|
+
initializer "rack_mini_profiler.configure_rails_initialization" do |app|
|
48
|
+
Rack::MiniProfilerRails.initialize!(app)
|
45
49
|
end
|
46
50
|
|
47
51
|
# TODO: Implement something better here
|
48
|
-
# config.after_initialize do
|
49
|
-
#
|
50
|
-
# class ::ActionView::Helpers::AssetTagHelper::JavascriptIncludeTag
|
52
|
+
# config.after_initialize do
|
53
|
+
#
|
54
|
+
# class ::ActionView::Helpers::AssetTagHelper::JavascriptIncludeTag
|
51
55
|
# alias_method :asset_tag_orig, :asset_tag
|
52
56
|
# def asset_tag(source,options)
|
53
|
-
# current = Rack::MiniProfiler.current
|
54
|
-
# return asset_tag_orig(source,options) unless current
|
57
|
+
# current = Rack::MiniProfiler.current
|
58
|
+
# return asset_tag_orig(source,options) unless current
|
55
59
|
# wrapped = ""
|
56
60
|
# unless current.mpt_init
|
57
61
|
# current.mpt_init = true
|
58
|
-
# wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
|
62
|
+
# wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
|
59
63
|
# end
|
60
64
|
# name = source.split('/')[-1]
|
61
65
|
# wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
|
@@ -63,15 +67,15 @@ module MiniProfilerRails
|
|
63
67
|
# end
|
64
68
|
# end
|
65
69
|
|
66
|
-
# class ::ActionView::Helpers::AssetTagHelper::StylesheetIncludeTag
|
70
|
+
# class ::ActionView::Helpers::AssetTagHelper::StylesheetIncludeTag
|
67
71
|
# alias_method :asset_tag_orig, :asset_tag
|
68
72
|
# def asset_tag(source,options)
|
69
|
-
# current = Rack::MiniProfiler.current
|
70
|
-
# return asset_tag_orig(source,options) unless current
|
73
|
+
# current = Rack::MiniProfiler.current
|
74
|
+
# return asset_tag_orig(source,options) unless current
|
71
75
|
# wrapped = ""
|
72
76
|
# unless current.mpt_init
|
73
77
|
# current.mpt_init = true
|
74
|
-
# wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
|
78
|
+
# wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
|
75
79
|
# end
|
76
80
|
# name = source.split('/')[-1]
|
77
81
|
# wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
|