rack-mini-profiler 0.1.6 → 0.1.11.pre
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.
- data/CHANGELOG +23 -0
- data/README.md +25 -0
- data/lib/html/includes.css +75 -1
- data/lib/html/includes.js +14 -2
- data/lib/html/includes.less +1 -1
- data/lib/html/profile_handler.js +9 -9
- data/lib/mini_profiler/client_timer_struct.rb +5 -3
- data/lib/mini_profiler/config.rb +2 -1
- data/lib/mini_profiler/page_timer_struct.rb +1 -1
- data/lib/mini_profiler/profiler.rb +91 -23
- data/lib/mini_profiler/storage/file_store.rb +9 -7
- data/lib/mini_profiler/storage/memory_store.rb +0 -4
- data/lib/mini_profiler/timer_struct.rb +3 -1
- data/lib/mini_profiler_rails/railtie.rb +3 -1
- data/lib/patches/sql_patches.rb +82 -30
- data/lib/rack-mini-profiler.rb +3 -3
- data/rack-mini-profiler.gemspec +3 -4
- metadata +6 -11
- data/lib/html/MiniProfilerHandler.cs +0 -419
- data/lib/mini_profiler/body_add_proxy.rb +0 -45
@@ -13,6 +13,7 @@ module Rack
|
|
13
13
|
data = ::File.open(path(key),"rb") {|f| f.read}
|
14
14
|
return Marshal.load data
|
15
15
|
rescue => e
|
16
|
+
p e
|
16
17
|
return nil
|
17
18
|
end
|
18
19
|
end
|
@@ -39,9 +40,14 @@ module Rack
|
|
39
40
|
|
40
41
|
me = self
|
41
42
|
Thread.new do
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
begin
|
44
|
+
while true do
|
45
|
+
# TODO: a sane retry count before bailing
|
46
|
+
me.cleanup_cache
|
47
|
+
sleep(3600)
|
48
|
+
end
|
49
|
+
rescue
|
50
|
+
# don't crash the thread, we can clean up next time
|
45
51
|
end
|
46
52
|
end
|
47
53
|
end
|
@@ -83,10 +89,6 @@ module Rack
|
|
83
89
|
}
|
84
90
|
end
|
85
91
|
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
|
90
92
|
def cleanup_cache
|
91
93
|
files = Dir.entries(@path)
|
92
94
|
@timer_struct_lock.synchronize {
|
@@ -22,7 +22,9 @@ module Rack
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def to_json(*a)
|
25
|
-
|
25
|
+
# this does could take in an option hash, but the only interesting there is max_nesting.
|
26
|
+
# if this becomes an option we could increase
|
27
|
+
::JSON.generate( @attributes, :max_nesting => 100 )
|
26
28
|
end
|
27
29
|
|
28
30
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
1
3
|
module MiniProfilerRails
|
2
4
|
|
3
5
|
class Railtie < ::Rails::Railtie
|
@@ -22,7 +24,7 @@ module MiniProfilerRails
|
|
22
24
|
|
23
25
|
# The file store is just so much less flaky
|
24
26
|
tmp = Rails.root.to_s + "/tmp/miniprofiler"
|
25
|
-
|
27
|
+
FileUtils.mkdir_p(tmp) unless File.exists?(tmp)
|
26
28
|
|
27
29
|
c.storage_options = {:path => tmp}
|
28
30
|
c.storage = Rack::MiniProfiler::FileStore
|
data/lib/patches/sql_patches.rb
CHANGED
@@ -13,6 +13,12 @@ class SqlPatches
|
|
13
13
|
rescue NameError
|
14
14
|
false
|
15
15
|
end
|
16
|
+
|
17
|
+
def self.module_exists?(name)
|
18
|
+
eval(name + ".class").to_s.eql?('Module')
|
19
|
+
rescue NameError
|
20
|
+
false
|
21
|
+
end
|
16
22
|
end
|
17
23
|
|
18
24
|
# The best kind of instrumentation is in the actual db provider, however we don't want to double instrument
|
@@ -85,6 +91,23 @@ if SqlPatches.class_exists? "PG::Result"
|
|
85
91
|
class PG::Connection
|
86
92
|
alias_method :exec_without_profiling, :exec
|
87
93
|
alias_method :async_exec_without_profiling, :async_exec
|
94
|
+
alias_method :exec_prepared_without_profiling, :exec_prepared
|
95
|
+
alias_method :send_query_prepared_without_profiling, :send_query_prepared
|
96
|
+
alias_method :prepare_without_profiling, :prepare
|
97
|
+
|
98
|
+
def prepare(*args,&blk)
|
99
|
+
current = ::Rack::MiniProfiler.current
|
100
|
+
return prepare_without_profiling(*args,&blk) unless current
|
101
|
+
|
102
|
+
@prepare_map ||= {}
|
103
|
+
@prepare_map[args[0]] = args[1]
|
104
|
+
|
105
|
+
# dont leak more than 10k ever
|
106
|
+
@prepare_map = {} if @prepare_map.length > 10000
|
107
|
+
|
108
|
+
prepare_without_profiling(*args,&blk)
|
109
|
+
|
110
|
+
end
|
88
111
|
|
89
112
|
def exec(*args,&blk)
|
90
113
|
current = ::Rack::MiniProfiler.current
|
@@ -98,6 +121,34 @@ if SqlPatches.class_exists? "PG::Result"
|
|
98
121
|
result
|
99
122
|
end
|
100
123
|
|
124
|
+
def exec_prepared(*args,&blk)
|
125
|
+
current = ::Rack::MiniProfiler.current
|
126
|
+
return exec_prepared_without_profiling(*args,&blk) unless current
|
127
|
+
|
128
|
+
start = Time.now
|
129
|
+
result = exec_prepared_without_profiling(*args,&blk)
|
130
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
131
|
+
mapped = args[0]
|
132
|
+
mapped = @prepare_map[mapped] || args[0] if @prepare_map
|
133
|
+
result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(mapped, elapsed_time))
|
134
|
+
|
135
|
+
result
|
136
|
+
end
|
137
|
+
|
138
|
+
def send_query_prepared(*args,&blk)
|
139
|
+
current = ::Rack::MiniProfiler.current
|
140
|
+
return send_query_prepared_without_profiling(*args,&blk) unless current
|
141
|
+
|
142
|
+
start = Time.now
|
143
|
+
result = send_query_prepared_without_profiling(*args,&blk)
|
144
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
145
|
+
mapped = args[0]
|
146
|
+
mapped = @prepare_map[mapped] || args[0] if @prepare_map
|
147
|
+
result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(mapped, elapsed_time))
|
148
|
+
|
149
|
+
result
|
150
|
+
end
|
151
|
+
|
101
152
|
def async_exec(*args,&blk)
|
102
153
|
current = ::Rack::MiniProfiler.current
|
103
154
|
return exec_without_profiling(*args,&blk) unless current
|
@@ -134,45 +185,46 @@ end
|
|
134
185
|
|
135
186
|
## based off https://github.com/newrelic/rpm/blob/master/lib/new_relic/agent/instrumentation/active_record.rb
|
136
187
|
## fallback for alls sorts of weird dbs
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
instrumented_class
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
188
|
+
if SqlPatches.module_exists?('ActiveRecord')
|
189
|
+
module Rack
|
190
|
+
class MiniProfiler
|
191
|
+
module ActiveRecordInstrumentation
|
192
|
+
def self.included(instrumented_class)
|
193
|
+
instrumented_class.class_eval do
|
194
|
+
unless instrumented_class.method_defined?(:log_without_miniprofiler)
|
195
|
+
alias_method :log_without_miniprofiler, :log
|
196
|
+
alias_method :log, :log_with_miniprofiler
|
197
|
+
protected :log
|
198
|
+
end
|
146
199
|
end
|
147
200
|
end
|
148
|
-
end
|
149
201
|
|
150
|
-
|
151
|
-
|
152
|
-
|
202
|
+
def log_with_miniprofiler(*args, &block)
|
203
|
+
current = ::Rack::MiniProfiler.current
|
204
|
+
return log_without_miniprofiler(*args, &block) unless current
|
153
205
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
206
|
+
sql, name, binds = args
|
207
|
+
t0 = Time.now
|
208
|
+
rval = log_without_miniprofiler(*args, &block)
|
209
|
+
|
210
|
+
# Don't log schema queries if the option is set
|
211
|
+
return rval if Rack::MiniProfiler.config.skip_schema_queries and name =~ /SCHEMA/
|
160
212
|
|
161
|
-
|
162
|
-
|
163
|
-
|
213
|
+
elapsed_time = ((Time.now - t0).to_f * 1000).round(1)
|
214
|
+
Rack::MiniProfiler.record_sql(sql, elapsed_time)
|
215
|
+
rval
|
216
|
+
end
|
164
217
|
end
|
165
218
|
end
|
166
|
-
end
|
167
219
|
|
168
|
-
|
169
|
-
|
170
|
-
|
220
|
+
def self.insert_instrumentation
|
221
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
|
222
|
+
include ::Rack::MiniProfiler::ActiveRecordInstrumentation
|
223
|
+
end
|
171
224
|
end
|
172
|
-
end
|
173
225
|
|
174
|
-
|
175
|
-
|
226
|
+
if defined?(::Rails) && !SqlPatches.patched?
|
227
|
+
insert_instrumentation
|
228
|
+
end
|
176
229
|
end
|
177
230
|
end
|
178
|
-
|
data/lib/rack-mini-profiler.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'mini_profiler/profiler'
|
2
|
+
require 'patches/sql_patches'
|
3
3
|
|
4
4
|
if defined?(::Rails) && ::Rails::VERSION::MAJOR.to_i == 3
|
5
|
-
require
|
5
|
+
require 'mini_profiler_rails/railtie'
|
6
6
|
end
|
data/rack-mini-profiler.gemspec
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "rack-mini-profiler"
|
3
|
-
s.version = "0.1.
|
3
|
+
s.version = "0.1.11.pre"
|
4
4
|
s.summary = "Profiles loading speed for rack applications."
|
5
5
|
s.authors = ["Aleks Totic","Sam Saffron", "Robin Ward"]
|
6
|
-
s.date = "2012-04-02"
|
7
6
|
s.description = "Page loading speed displayed on every page. Optimize while you develop, performance is a feature."
|
8
7
|
s.email = "sam.saffron@gmail.com"
|
9
8
|
s.homepage = "http://miniprofiler.com"
|
@@ -14,9 +13,9 @@ Gem::Specification.new do |s|
|
|
14
13
|
"README.md",
|
15
14
|
"CHANGELOG"
|
16
15
|
]
|
17
|
-
s.add_runtime_dependency 'rack', '>= 1.1.3'
|
16
|
+
s.add_runtime_dependency 'rack', '>= 1.1.3'
|
18
17
|
if RUBY_VERSION < "1.9"
|
19
|
-
s.add_runtime_dependency 'json', '>= 1.6'
|
18
|
+
s.add_runtime_dependency 'json', '>= 1.6'
|
20
19
|
end
|
21
20
|
|
22
21
|
s.add_development_dependency 'rake'
|
metadata
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-mini-profiler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.11.pre
|
5
|
+
prerelease: 7
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Aleks Totic
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2012-
|
14
|
+
date: 2012-08-10 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rack
|
@@ -93,7 +93,6 @@ files:
|
|
93
93
|
- lib/mini_profiler/page_timer_struct.rb
|
94
94
|
- lib/mini_profiler/context.rb
|
95
95
|
- lib/mini_profiler/config.rb
|
96
|
-
- lib/mini_profiler/body_add_proxy.rb
|
97
96
|
- lib/mini_profiler/profiling_methods.rb
|
98
97
|
- lib/mini_profiler/client_timer_struct.rb
|
99
98
|
- lib/mini_profiler/profiler.rb
|
@@ -107,7 +106,6 @@ files:
|
|
107
106
|
- lib/html/share.html
|
108
107
|
- lib/html/includes.less
|
109
108
|
- lib/html/list.css
|
110
|
-
- lib/html/MiniProfilerHandler.cs
|
111
109
|
- lib/html/includes.js
|
112
110
|
- lib/html/jquery.tmpl.js
|
113
111
|
- lib/html/list.tmpl
|
@@ -132,16 +130,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
132
130
|
version: '0'
|
133
131
|
segments:
|
134
132
|
- 0
|
135
|
-
hash: -
|
133
|
+
hash: -126757873
|
136
134
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
135
|
none: false
|
138
136
|
requirements:
|
139
|
-
- - ! '
|
137
|
+
- - ! '>'
|
140
138
|
- !ruby/object:Gem::Version
|
141
|
-
version:
|
142
|
-
segments:
|
143
|
-
- 0
|
144
|
-
hash: -974819475
|
139
|
+
version: 1.3.1
|
145
140
|
requirements: []
|
146
141
|
rubyforge_project:
|
147
142
|
rubygems_version: 1.8.24
|
@@ -1,419 +0,0 @@
|
|
1
|
-
using System;
|
2
|
-
using System.Collections.Generic;
|
3
|
-
using System.IO;
|
4
|
-
using System.Web;
|
5
|
-
using System.Web.Routing;
|
6
|
-
using System.Linq;
|
7
|
-
|
8
|
-
using StackExchange.Profiling.Helpers;
|
9
|
-
using System.Text;
|
10
|
-
using System.Collections.Concurrent;
|
11
|
-
|
12
|
-
namespace StackExchange.Profiling.UI
|
13
|
-
{
|
14
|
-
/// <summary>
|
15
|
-
/// Understands how to route and respond to MiniProfiler UI urls.
|
16
|
-
/// </summary>
|
17
|
-
public class MiniProfilerHandler : IRouteHandler, IHttpHandler
|
18
|
-
{
|
19
|
-
internal static HtmlString RenderIncludes(MiniProfiler profiler, RenderPosition? position = null, bool? showTrivial = null, bool? showTimeWithChildren = null, int? maxTracesToShow = null, bool? showControls = null, bool? useExistingjQuery = null)
|
20
|
-
{
|
21
|
-
string format = GetResource("include.partial.html");
|
22
|
-
|
23
|
-
var result = "";
|
24
|
-
|
25
|
-
if (profiler != null)
|
26
|
-
{
|
27
|
-
// HACK: unviewed ids are added to this list during Storage.Save, but we know we haven't see the current one yet,
|
28
|
-
// so go ahead and add it to the end - it's usually the only id, but if there was a redirect somewhere, it'll be there, too
|
29
|
-
MiniProfiler.Settings.EnsureStorageStrategy();
|
30
|
-
|
31
|
-
var authorized =
|
32
|
-
MiniProfiler.Settings.Results_Authorize == null ||
|
33
|
-
MiniProfiler.Settings.Results_Authorize(HttpContext.Current.Request);
|
34
|
-
|
35
|
-
List<Guid> ids;
|
36
|
-
if (authorized)
|
37
|
-
{
|
38
|
-
ids = MiniProfiler.Settings.Storage.GetUnviewedIds(profiler.User);
|
39
|
-
ids.Add(profiler.Id);
|
40
|
-
}
|
41
|
-
else
|
42
|
-
{
|
43
|
-
ids = new List<Guid> { profiler.Id };
|
44
|
-
}
|
45
|
-
|
46
|
-
result = format.Format(new
|
47
|
-
{
|
48
|
-
path = VirtualPathUtility.ToAbsolute(MiniProfiler.Settings.RouteBasePath).EnsureTrailingSlash(),
|
49
|
-
version = MiniProfiler.Settings.Version,
|
50
|
-
ids = ids.ToJson(),
|
51
|
-
position = (position ?? MiniProfiler.Settings.PopupRenderPosition).ToString().ToLower(),
|
52
|
-
showTrivial = showTrivial ?? MiniProfiler.Settings.PopupShowTrivial ? "true" : "false",
|
53
|
-
showChildren = showTimeWithChildren ?? MiniProfiler.Settings.PopupShowTimeWithChildren ? "true" : "false",
|
54
|
-
maxTracesToShow = maxTracesToShow ?? MiniProfiler.Settings.PopupMaxTracesToShow,
|
55
|
-
showControls = showControls ?? MiniProfiler.Settings.ShowControls ? "true" : "false",
|
56
|
-
currentId = profiler.Id,
|
57
|
-
authorized = authorized ? "true" : "false",
|
58
|
-
useExistingjQuery = useExistingjQuery ?? MiniProfiler.Settings.UseExistingjQuery ? "true" : "false"
|
59
|
-
});
|
60
|
-
|
61
|
-
}
|
62
|
-
|
63
|
-
return new HtmlString(result);
|
64
|
-
}
|
65
|
-
|
66
|
-
/// <summary>
|
67
|
-
/// Usually called internally, sometimes you may clear the routes during the apps lifecycle, if you do that call this to bring back mp
|
68
|
-
/// </summary>
|
69
|
-
public static void RegisterRoutes()
|
70
|
-
{
|
71
|
-
|
72
|
-
var routes = RouteTable.Routes;
|
73
|
-
var handler = new MiniProfilerHandler();
|
74
|
-
var prefix = MiniProfiler.Settings.RouteBasePath.Replace("~/", "").EnsureTrailingSlash();
|
75
|
-
|
76
|
-
using (routes.GetWriteLock())
|
77
|
-
{
|
78
|
-
var route = new Route(prefix + "{filename}", handler)
|
79
|
-
{
|
80
|
-
// we have to specify these, so no MVC route helpers will match, e.g. @Html.ActionLink("Home", "Index", "Home")
|
81
|
-
Defaults = new RouteValueDictionary( new { controller = "MiniProfilerHandler", action = "ProcessRequest" }),
|
82
|
-
Constraints = new RouteValueDictionary( new { controller = "MiniProfilerHandler", action = "ProcessRequest" })
|
83
|
-
};
|
84
|
-
|
85
|
-
// put our routes at the beginning, like a boss
|
86
|
-
routes.Insert(0, route);
|
87
|
-
}
|
88
|
-
}
|
89
|
-
|
90
|
-
/// <summary>
|
91
|
-
/// Returns this <see cref="MiniProfilerHandler"/> to handle <paramref name="requestContext"/>.
|
92
|
-
/// </summary>
|
93
|
-
public IHttpHandler GetHttpHandler(RequestContext requestContext)
|
94
|
-
{
|
95
|
-
return this; // elegant? I THINK SO.
|
96
|
-
}
|
97
|
-
|
98
|
-
/// <summary>
|
99
|
-
/// Try to keep everything static so we can easily be reused.
|
100
|
-
/// </summary>
|
101
|
-
public bool IsReusable
|
102
|
-
{
|
103
|
-
get { return true; }
|
104
|
-
}
|
105
|
-
|
106
|
-
/// <summary>
|
107
|
-
/// Returns either includes' css/javascript or results' html.
|
108
|
-
/// </summary>
|
109
|
-
public void ProcessRequest(HttpContext context)
|
110
|
-
{
|
111
|
-
string output;
|
112
|
-
string path = context.Request.AppRelativeCurrentExecutionFilePath;
|
113
|
-
|
114
|
-
switch (Path.GetFileNameWithoutExtension(path).ToLowerInvariant())
|
115
|
-
{
|
116
|
-
case "jquery.1.7.1":
|
117
|
-
case "jquery.tmpl":
|
118
|
-
case "includes":
|
119
|
-
case "list":
|
120
|
-
output = Includes(context, path);
|
121
|
-
break;
|
122
|
-
|
123
|
-
case "results-index":
|
124
|
-
output = Index(context);
|
125
|
-
break;
|
126
|
-
|
127
|
-
case "results-list":
|
128
|
-
output = ResultList(context);
|
129
|
-
break;
|
130
|
-
|
131
|
-
case "results":
|
132
|
-
output = Results(context);
|
133
|
-
break;
|
134
|
-
|
135
|
-
default:
|
136
|
-
output = NotFound(context);
|
137
|
-
break;
|
138
|
-
}
|
139
|
-
|
140
|
-
context.Response.Write(output);
|
141
|
-
}
|
142
|
-
|
143
|
-
private static string ResultList(HttpContext context)
|
144
|
-
{
|
145
|
-
string message;
|
146
|
-
if (!AuthorizeRequest(context, isList: true, message: out message))
|
147
|
-
{
|
148
|
-
return message;
|
149
|
-
}
|
150
|
-
|
151
|
-
var lastId = context.Request["last-id"];
|
152
|
-
Guid lastGuid = Guid.Empty;
|
153
|
-
|
154
|
-
if (!lastId.IsNullOrWhiteSpace()) {
|
155
|
-
Guid.TryParse(lastId, out lastGuid);
|
156
|
-
}
|
157
|
-
|
158
|
-
var guids = MiniProfiler.Settings.Storage.List(100);
|
159
|
-
|
160
|
-
if (lastGuid != Guid.Empty)
|
161
|
-
{
|
162
|
-
guids = guids.TakeWhile(g => g != lastGuid);
|
163
|
-
}
|
164
|
-
|
165
|
-
guids = guids.Reverse();
|
166
|
-
|
167
|
-
return guids.Select(g =>
|
168
|
-
{
|
169
|
-
var profiler = MiniProfiler.Settings.Storage.Load(g);
|
170
|
-
return new
|
171
|
-
{
|
172
|
-
profiler.Id,
|
173
|
-
profiler.Name,
|
174
|
-
profiler.DurationMilliseconds,
|
175
|
-
profiler.DurationMillisecondsInSql,
|
176
|
-
profiler.ClientTimings,
|
177
|
-
profiler.Started,
|
178
|
-
profiler.ExecutedNonQueries,
|
179
|
-
profiler.ExecutedReaders,
|
180
|
-
profiler.ExecutedScalars,
|
181
|
-
profiler.HasAllTrivialTimings,
|
182
|
-
profiler.HasDuplicateSqlTimings,
|
183
|
-
profiler.HasSqlTimings,
|
184
|
-
profiler.HasTrivialTimings,
|
185
|
-
profiler.HasUserViewed,
|
186
|
-
profiler.MachineName,
|
187
|
-
profiler.User
|
188
|
-
};
|
189
|
-
}
|
190
|
-
|
191
|
-
).ToJson();
|
192
|
-
}
|
193
|
-
|
194
|
-
private static string Index(HttpContext context)
|
195
|
-
{
|
196
|
-
string message;
|
197
|
-
if (!AuthorizeRequest(context, isList: true, message: out message))
|
198
|
-
{
|
199
|
-
return message;
|
200
|
-
}
|
201
|
-
|
202
|
-
context.Response.ContentType = "text/html";
|
203
|
-
|
204
|
-
var path = VirtualPathUtility.ToAbsolute(MiniProfiler.Settings.RouteBasePath).EnsureTrailingSlash();
|
205
|
-
|
206
|
-
return new StringBuilder()
|
207
|
-
.AppendLine("<html><head>")
|
208
|
-
.AppendFormat("<title>List of profiling sessions</title>")
|
209
|
-
.AppendLine()
|
210
|
-
.AppendLine("<script type='text/javascript' src='" + path + "jquery.1.7.1.js?v=" + MiniProfiler.Settings.Version + "'></script>")
|
211
|
-
.AppendLine("<script type='text/javascript' src='" + path + "jquery.tmpl.js?v=" + MiniProfiler.Settings.Version + "'></script>")
|
212
|
-
.AppendLine("<script type='text/javascript' src='" + path + "includes.js?v=" + MiniProfiler.Settings.Version + "'></script>")
|
213
|
-
.AppendLine("<script type='text/javascript' src='" + path + "list.js?v=" + MiniProfiler.Settings.Version + "'></script>")
|
214
|
-
.AppendLine("<link href='" + path +"list.css?v=" + MiniProfiler.Settings.Version + "' rel='stylesheet' type='text/css'>")
|
215
|
-
.AppendLine("<script type='text/javascript'>MiniProfiler.list.init({path: '" + path + "', version: '" + MiniProfiler.Settings.Version + "'})</script>")
|
216
|
-
.AppendLine("</head><body></body></html>")
|
217
|
-
.ToString();
|
218
|
-
}
|
219
|
-
|
220
|
-
/// <summary>
|
221
|
-
/// Handles rendering static content files.
|
222
|
-
/// </summary>
|
223
|
-
private static string Includes(HttpContext context, string path)
|
224
|
-
{
|
225
|
-
var response = context.Response;
|
226
|
-
|
227
|
-
switch (Path.GetExtension(path))
|
228
|
-
{
|
229
|
-
case ".js":
|
230
|
-
response.ContentType = "application/javascript";
|
231
|
-
break;
|
232
|
-
case ".css":
|
233
|
-
response.ContentType = "text/css";
|
234
|
-
break;
|
235
|
-
case ".tmpl":
|
236
|
-
response.ContentType = "text/x-jquery-tmpl";
|
237
|
-
break;
|
238
|
-
default:
|
239
|
-
return NotFound(context);
|
240
|
-
}
|
241
|
-
|
242
|
-
#if !DEBUG
|
243
|
-
var cache = response.Cache;
|
244
|
-
cache.SetCacheability(System.Web.HttpCacheability.Public);
|
245
|
-
cache.SetExpires(DateTime.Now.AddDays(7));
|
246
|
-
cache.SetValidUntilExpires(true);
|
247
|
-
#endif
|
248
|
-
|
249
|
-
|
250
|
-
var embeddedFile = Path.GetFileName(path);
|
251
|
-
return GetResource(embeddedFile);
|
252
|
-
}
|
253
|
-
|
254
|
-
/// <summary>
|
255
|
-
/// Handles rendering a previous MiniProfiler session, identified by its "?id=GUID" on the query.
|
256
|
-
/// </summary>
|
257
|
-
private static string Results(HttpContext context)
|
258
|
-
{
|
259
|
-
// when we're rendering as a button/popup in the corner, we'll pass ?popup=1
|
260
|
-
// if it's absent, we're rendering results as a full page for sharing
|
261
|
-
var isPopup = !string.IsNullOrWhiteSpace(context.Request["popup"]);
|
262
|
-
|
263
|
-
// this guid is the MiniProfiler.Id property
|
264
|
-
Guid id;
|
265
|
-
if (!Guid.TryParse(context.Request["id"], out id))
|
266
|
-
return isPopup ? NotFound(context) : NotFound(context, "text/plain", "No Guid id specified on the query string");
|
267
|
-
|
268
|
-
MiniProfiler.Settings.EnsureStorageStrategy();
|
269
|
-
var profiler = MiniProfiler.Settings.Storage.Load(id);
|
270
|
-
|
271
|
-
var provider = WebRequestProfilerProvider.Settings.UserProvider;
|
272
|
-
string user = null;
|
273
|
-
if (provider != null)
|
274
|
-
{
|
275
|
-
user = provider.GetUser(context.Request);
|
276
|
-
}
|
277
|
-
|
278
|
-
MiniProfiler.Settings.Storage.SetViewed(user, id);
|
279
|
-
|
280
|
-
if (profiler == null)
|
281
|
-
{
|
282
|
-
return isPopup ? NotFound(context) : NotFound(context, "text/plain", "No MiniProfiler results found with Id=" + id.ToString());
|
283
|
-
}
|
284
|
-
|
285
|
-
bool needsSave = false;
|
286
|
-
if (profiler.ClientTimings == null)
|
287
|
-
{
|
288
|
-
profiler.ClientTimings = ClientTimings.FromRequest(context.Request);
|
289
|
-
if (profiler.ClientTimings != null)
|
290
|
-
{
|
291
|
-
needsSave = true;
|
292
|
-
}
|
293
|
-
}
|
294
|
-
|
295
|
-
if (profiler.HasUserViewed == false)
|
296
|
-
{
|
297
|
-
profiler.HasUserViewed = true;
|
298
|
-
needsSave = true;
|
299
|
-
}
|
300
|
-
|
301
|
-
if (needsSave) MiniProfiler.Settings.Storage.Save(profiler);
|
302
|
-
|
303
|
-
|
304
|
-
var authorize = MiniProfiler.Settings.Results_Authorize;
|
305
|
-
|
306
|
-
|
307
|
-
if (authorize != null && !authorize(context.Request))
|
308
|
-
{
|
309
|
-
context.Response.ContentType = "application/json";
|
310
|
-
return "hidden".ToJson();
|
311
|
-
}
|
312
|
-
|
313
|
-
return isPopup ? ResultsJson(context, profiler) : ResultsFullPage(context, profiler);
|
314
|
-
}
|
315
|
-
|
316
|
-
private static bool AuthorizeRequest(HttpContext context, bool isList, out string message)
|
317
|
-
{
|
318
|
-
message = null;
|
319
|
-
var authorize = MiniProfiler.Settings.Results_Authorize;
|
320
|
-
var authorizeList = MiniProfiler.Settings.Results_List_Authorize;
|
321
|
-
|
322
|
-
if (authorize != null && !authorize(context.Request) || (isList && (authorizeList == null || !authorizeList(context.Request))))
|
323
|
-
{
|
324
|
-
context.Response.StatusCode = 401;
|
325
|
-
context.Response.ContentType = "text/plain";
|
326
|
-
message = "unauthorized";
|
327
|
-
return false;
|
328
|
-
}
|
329
|
-
return true;
|
330
|
-
}
|
331
|
-
|
332
|
-
private static string ResultsJson(HttpContext context, MiniProfiler profiler)
|
333
|
-
{
|
334
|
-
context.Response.ContentType = "application/json";
|
335
|
-
return MiniProfiler.ToJson(profiler);
|
336
|
-
}
|
337
|
-
|
338
|
-
private static string ResultsFullPage(HttpContext context, MiniProfiler profiler)
|
339
|
-
{
|
340
|
-
context.Response.ContentType = "text/html";
|
341
|
-
|
342
|
-
var template = GetResource("share.html");
|
343
|
-
return template.Format(new
|
344
|
-
{
|
345
|
-
name = profiler.Name,
|
346
|
-
duration = profiler.DurationMilliseconds.ToString(),
|
347
|
-
path = VirtualPathUtility.ToAbsolute(MiniProfiler.Settings.RouteBasePath).EnsureTrailingSlash(),
|
348
|
-
json = MiniProfiler.ToJson(profiler),
|
349
|
-
includes = RenderIncludes(profiler),
|
350
|
-
version = MiniProfiler.Settings.Version
|
351
|
-
});
|
352
|
-
}
|
353
|
-
|
354
|
-
private static bool bypassLocalLoad = false;
|
355
|
-
private static string GetResource(string filename)
|
356
|
-
{
|
357
|
-
filename = filename.ToLower();
|
358
|
-
string result;
|
359
|
-
|
360
|
-
#if DEBUG
|
361
|
-
// attempt to simply load from file system, this lets up modify js without needing to recompile A MILLION TIMES
|
362
|
-
if (!bypassLocalLoad)
|
363
|
-
{
|
364
|
-
|
365
|
-
var trace = new System.Diagnostics.StackTrace(true);
|
366
|
-
var path = System.IO.Path.GetDirectoryName(trace.GetFrames()[0].GetFileName()) + "\\..\\UI\\" + filename;
|
367
|
-
try
|
368
|
-
{
|
369
|
-
return File.ReadAllText(path);
|
370
|
-
}
|
371
|
-
catch
|
372
|
-
{
|
373
|
-
bypassLocalLoad = true;
|
374
|
-
}
|
375
|
-
}
|
376
|
-
|
377
|
-
#endif
|
378
|
-
|
379
|
-
if (!_ResourceCache.TryGetValue(filename, out result))
|
380
|
-
{
|
381
|
-
string customTemplatesPath = HttpContext.Current.Server.MapPath(MiniProfiler.Settings.CustomUITemplates);
|
382
|
-
string customTemplateFile = System.IO.Path.Combine(customTemplatesPath, filename);
|
383
|
-
|
384
|
-
if (System.IO.File.Exists(customTemplateFile))
|
385
|
-
{
|
386
|
-
result = File.ReadAllText(customTemplateFile);
|
387
|
-
}
|
388
|
-
else
|
389
|
-
{
|
390
|
-
using (var stream = typeof(MiniProfilerHandler).Assembly.GetManifestResourceStream("StackExchange.Profiling.UI." + filename))
|
391
|
-
using (var reader = new StreamReader(stream))
|
392
|
-
{
|
393
|
-
result = reader.ReadToEnd();
|
394
|
-
}
|
395
|
-
}
|
396
|
-
_ResourceCache[filename] = result;
|
397
|
-
}
|
398
|
-
|
399
|
-
return result;
|
400
|
-
}
|
401
|
-
|
402
|
-
/// <summary>
|
403
|
-
/// Embedded resource contents keyed by filename.
|
404
|
-
/// </summary>
|
405
|
-
private static readonly ConcurrentDictionary<string, string> _ResourceCache = new ConcurrentDictionary<string, string>();
|
406
|
-
|
407
|
-
/// <summary>
|
408
|
-
/// Helper method that sets a proper 404 response code.
|
409
|
-
/// </summary>
|
410
|
-
private static string NotFound(HttpContext context, string contentType = "text/plain", string message = null)
|
411
|
-
{
|
412
|
-
context.Response.StatusCode = 404;
|
413
|
-
context.Response.ContentType = contentType;
|
414
|
-
|
415
|
-
return message;
|
416
|
-
}
|
417
|
-
|
418
|
-
}
|
419
|
-
}
|