Commit 8ff0f8ad authored by Roy Zwambag's avatar Roy Zwambag Committed by Aleksei Lipniagov

Add sampling mode buttons for flamegraph in the performance bar

parent 3dd6d105
...@@ -124,9 +124,6 @@ export default { ...@@ -124,9 +124,6 @@ export default {
const fileName = this.requests[0].truncatedUrl; const fileName = this.requests[0].truncatedUrl;
return `${fileName}_perf_bar_${Date.now()}.json`; return `${fileName}_perf_bar_${Date.now()}.json`;
}, },
flamegraphPath() {
return mergeUrlParams({ performance_bar: 'flamegraph' }, window.location.href);
},
}, },
mounted() { mounted() {
this.currentRequest = this.requestId; this.currentRequest = this.requestId;
...@@ -135,6 +132,12 @@ export default { ...@@ -135,6 +132,12 @@ export default {
changeCurrentRequest(newRequestId) { changeCurrentRequest(newRequestId) {
this.currentRequest = newRequestId; this.currentRequest = newRequestId;
}, },
flamegraphPath(mode) {
return mergeUrlParams(
{ performance_bar: 'flamegraph', stackprof_mode: mode },
window.location.href,
);
},
}, },
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] }, safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
}; };
...@@ -180,8 +183,17 @@ export default { ...@@ -180,8 +183,17 @@ export default {
}}</a> }}</a>
</div> </div>
<div v-if="currentRequest.details" id="peek-flamegraph" class="view"> <div v-if="currentRequest.details" id="peek-flamegraph" class="view">
<a class="gl-text-blue-200" :href="flamegraphPath">{{ <span class="gl-text-white-200">{{ s__('PerformanceBar|Flamegraph with mode:') }}</span>
s__('PerformanceBar|Flamegraph') <a class="gl-text-blue-200" :href="flamegraphPath('wall')">{{
s__('PerformanceBar|wall')
}}</a>
/
<a class="gl-text-blue-200" :href="flamegraphPath('cpu')">{{
s__('PerformanceBar|cpu')
}}</a>
/
<a class="gl-text-blue-200" :href="flamegraphPath('object')">{{
s__('PerformanceBar|object')
}}</a> }}</a>
</div> </div>
<a v-if="statsUrl" class="gl-text-blue-200 view" :href="statsUrl">{{ <a v-if="statsUrl" class="gl-text-blue-200 view" :href="statsUrl">{{
......
...@@ -65,7 +65,11 @@ From left to right, the performance bar displays: ...@@ -65,7 +65,11 @@ From left to right, the performance bar displays:
can be added by its full URL (authenticated as the current user), or by the value of can be added by its full URL (authenticated as the current user), or by the value of
its `X-Request-Id` header. its `X-Request-Id` header.
- **Download**: a link to download the raw JSON used to generate the Performance Bar reports. - **Download**: a link to download the raw JSON used to generate the Performance Bar reports.
- **Flamegraph**: a link to generate a [flamegraph](../../../development/profiling.md#speedscope-flamegraphs) of the current URL. - **Flamegraph** with mode: a link to generate a [flamegraph](../../../development/profiling.md#speedscope-flamegraphs)
of the current URL with the selected [Stackprof mode](https://github.com/tmm1/stackprof#sampling):
- The **Wall** mode samples every *interval* of the time on a clock on a wall. The interval is set to `10100` microseconds.
- The **CPU** mode samples every *interval* of CPU activity. The interval is set to `10100` microseconds.
- The **Object** mode samples every *interval*. The interval is set to `100` allocations.
- **Request Selector**: a select box displayed on the right-hand side of the - **Request Selector**: a select box displayed on the right-hand side of the
Performance Bar which enables you to view these metrics for any requests made while Performance Bar which enables you to view these metrics for any requests made while
the current page was open. Only the first two requests per unique URL are captured. the current page was open. Only the first two requests per unique URL are captured.
......
...@@ -98,11 +98,13 @@ profile and log output to S3. ...@@ -98,11 +98,13 @@ profile and log output to S3.
## Speedscope flamegraphs ## Speedscope flamegraphs
You can generate a flamegraph for a particular URL by selecting the flamegraph button in the performance bar or by adding the `performance_bar=flamegraph` parameter to the request. You can generate a flamegraph for a particular URL by selecting a flamegraph sampling mode button in the performance bar or by adding the `performance_bar=flamegraph` parameter to the request.
![Speedscope](img/speedscope_v13_12.png) ![Speedscope](img/speedscope_v13_12.png)
More information about the views can be found in the [Speedscope docs](https://github.com/jlfwong/speedscope#views) Find more information about the views in the [Speedscope docs](https://github.com/jlfwong/speedscope#views).
Find more information about different sampling modes in the [Stackprof docs](https://github.com/tmm1/stackprof#sampling).
This is enabled for all users that can access the performance bar. This is enabled for all users that can access the performance bar.
......
...@@ -19,11 +19,12 @@ module Gitlab ...@@ -19,11 +19,12 @@ module Gitlab
require 'stackprof' require 'stackprof'
begin begin
mode = stackprof_mode(request)
flamegraph = ::StackProf.run( flamegraph = ::StackProf.run(
mode: :wall, mode: mode,
raw: true, raw: true,
aggregate: false, aggregate: false,
interval: ::Gitlab::StackProf::DEFAULT_INTERVAL_US interval: ::Gitlab::StackProf.interval(mode)
) do ) do
_, _, body = @app.call(env) _, _, body = @app.call(env)
end end
...@@ -64,7 +65,7 @@ module Gitlab ...@@ -64,7 +65,7 @@ module Gitlab
var iframe = document.createElement('IFRAME'); var iframe = document.createElement('IFRAME');
iframe.setAttribute('id', 'speedscope-iframe'); iframe.setAttribute('id', 'speedscope-iframe');
document.body.appendChild(iframe); document.body.appendChild(iframe);
var iframeUrl = '#{speedscope_path}#profileURL=' + objUrl + '&title=' + 'Flamegraph for #{CGI.escape(path)}'; var iframeUrl = '#{speedscope_path}#profileURL=' + objUrl + '&title=' + 'Flamegraph for #{CGI.escape(path)} in #{stackprof_mode(request)} mode';
iframe.setAttribute('src', iframeUrl); iframe.setAttribute('src', iframeUrl);
</script> </script>
</body> </body>
...@@ -73,6 +74,17 @@ module Gitlab ...@@ -73,6 +74,17 @@ module Gitlab
[200, headers, [html]] [200, headers, [html]]
end end
def stackprof_mode(request)
case request.params['stackprof_mode']&.to_sym
when :cpu
:cpu
when :object
:object
else
:wall
end
end
end end
end end
end end
...@@ -75,20 +75,20 @@ module Gitlab ...@@ -75,20 +75,20 @@ module Gitlab
current_timeout_s = nil current_timeout_s = nil
else else
mode = ENV['STACKPROF_MODE']&.to_sym || DEFAULT_MODE mode = ENV['STACKPROF_MODE']&.to_sym || DEFAULT_MODE
interval = ENV['STACKPROF_INTERVAL']&.to_i stackprof_interval = ENV['STACKPROF_INTERVAL']&.to_i
interval ||= (mode == :object ? DEFAULT_INTERVAL_EVENTS : DEFAULT_INTERVAL_US) stackprof_interval ||= interval(mode)
log_event( log_event(
'starting profile', 'starting profile',
profile_mode: mode, profile_mode: mode,
profile_interval: interval, profile_interval: stackprof_interval,
profile_timeout: timeout_s profile_timeout: timeout_s
) )
::StackProf.start( ::StackProf.start(
mode: mode, mode: mode,
raw: Gitlab::Utils.to_boolean(ENV['STACKPROF_RAW'] || 'true'), raw: Gitlab::Utils.to_boolean(ENV['STACKPROF_RAW'] || 'true'),
interval: interval interval: stackprof_interval
) )
current_timeout_s = timeout_s current_timeout_s = timeout_s
end end
...@@ -131,5 +131,9 @@ module Gitlab ...@@ -131,5 +131,9 @@ module Gitlab
pid: Process.pid pid: Process.pid
}.merge(labels.compact)) }.merge(labels.compact))
end end
def self.interval(mode)
mode == :object ? DEFAULT_INTERVAL_EVENTS : DEFAULT_INTERVAL_US
end
end end
end end
...@@ -24649,7 +24649,7 @@ msgstr "" ...@@ -24649,7 +24649,7 @@ msgstr ""
msgid "PerformanceBar|First Contentful Paint" msgid "PerformanceBar|First Contentful Paint"
msgstr "" msgstr ""
msgid "PerformanceBar|Flamegraph" msgid "PerformanceBar|Flamegraph with mode:"
msgstr "" msgstr ""
msgid "PerformanceBar|Frontend resources" msgid "PerformanceBar|Frontend resources"
...@@ -24685,6 +24685,15 @@ msgstr "" ...@@ -24685,6 +24685,15 @@ msgstr ""
msgid "PerformanceBar|Trace" msgid "PerformanceBar|Trace"
msgstr "" msgstr ""
msgid "PerformanceBar|cpu"
msgstr ""
msgid "PerformanceBar|object"
msgstr ""
msgid "PerformanceBar|wall"
msgstr ""
msgid "Period in seconds" msgid "Period in seconds"
msgstr "" msgstr ""
......
...@@ -46,7 +46,7 @@ RSpec.describe Gitlab::Middleware::Speedscope do ...@@ -46,7 +46,7 @@ RSpec.describe Gitlab::Middleware::Speedscope do
allow(env).to receive(:[]).with('warden').and_return(double('Warden', user: create(:admin))) allow(env).to receive(:[]).with('warden').and_return(double('Warden', user: create(:admin)))
end end
it 'runs StackProf and returns a flamegraph' do it 'returns a flamegraph' do
expect(StackProf).to receive(:run).and_call_original expect(StackProf).to receive(:run).and_call_original
status, headers, body = middleware.call(env) status, headers, body = middleware.call(env)
...@@ -55,6 +55,56 @@ RSpec.describe Gitlab::Middleware::Speedscope do ...@@ -55,6 +55,56 @@ RSpec.describe Gitlab::Middleware::Speedscope do
expect(headers).to eq({ 'Content-Type' => 'text/html' }) expect(headers).to eq({ 'Content-Type' => 'text/html' })
expect(body.first).to include('speedscope-iframe') expect(body.first).to include('speedscope-iframe')
end end
context 'when the stackprof_mode parameter is set and valid' do
let(:env) { Rack::MockRequest.env_for('/', params: { 'performance_bar' => 'flamegraph', 'stackprof_mode' => 'cpu' }) }
it 'runs StackProf in the mode specified in the stackprof_mode parameter' do
expect(StackProf).to receive(:run).with(hash_including(mode: :cpu))
middleware.call(env)
end
end
context 'when the stackprof_mode parameter is not set' do
let(:env) { Rack::MockRequest.env_for('/', params: { 'performance_bar' => 'flamegraph' }) }
it 'runs StackProf in wall mode' do
expect(StackProf).to receive(:run).with(hash_including(mode: :wall))
middleware.call(env)
end
end
context 'when the stackprof_mode parameter is invalid' do
let(:env) { Rack::MockRequest.env_for('/', params: { 'performance_bar' => 'flamegraph', 'stackprof_mode' => 'invalid' }) }
it 'runs StackProf in wall mode' do
expect(StackProf).to receive(:run).with(hash_including(mode: :wall))
middleware.call(env)
end
end
context 'when the stackprof_mode parameter is set to object mode' do
let(:env) { Rack::MockRequest.env_for('/', params: { 'performance_bar' => 'flamegraph', 'stackprof_mode' => 'object' }) }
it 'runs StackProf with an interval of 100' do
expect(StackProf).to receive(:run).with(hash_including(interval: 100))
middleware.call(env)
end
end
context 'when the stackprof_mode parameter is not set to object mode' do
let(:env) { Rack::MockRequest.env_for('/', params: { 'performance_bar' => 'flamegraph', 'stackprof_mode' => 'wall' }) }
it 'runs StackProf with an interval of 10_100' do
expect(StackProf).to receive(:run).with(hash_including(interval: 10_100))
middleware.call(env)
end
end
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment