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 {
const fileName = this.requests[0].truncatedUrl;
return `${fileName}_perf_bar_${Date.now()}.json`;
},
flamegraphPath() {
return mergeUrlParams({ performance_bar: 'flamegraph' }, window.location.href);
},
},
mounted() {
this.currentRequest = this.requestId;
......@@ -135,6 +132,12 @@ export default {
changeCurrentRequest(newRequestId) {
this.currentRequest = newRequestId;
},
flamegraphPath(mode) {
return mergeUrlParams(
{ performance_bar: 'flamegraph', stackprof_mode: mode },
window.location.href,
);
},
},
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
};
......@@ -180,8 +183,17 @@ export default {
}}</a>
</div>
<div v-if="currentRequest.details" id="peek-flamegraph" class="view">
<a class="gl-text-blue-200" :href="flamegraphPath">{{
s__('PerformanceBar|Flamegraph')
<span class="gl-text-white-200">{{ s__('PerformanceBar|Flamegraph with mode:') }}</span>
<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>
</div>
<a v-if="statsUrl" class="gl-text-blue-200 view" :href="statsUrl">{{
......
......@@ -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
its `X-Request-Id` header.
- **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
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.
......
......@@ -98,11 +98,13 @@ profile and log output to S3.
## 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)
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.
......
......@@ -19,11 +19,12 @@ module Gitlab
require 'stackprof'
begin
mode = stackprof_mode(request)
flamegraph = ::StackProf.run(
mode: :wall,
mode: mode,
raw: true,
aggregate: false,
interval: ::Gitlab::StackProf::DEFAULT_INTERVAL_US
interval: ::Gitlab::StackProf.interval(mode)
) do
_, _, body = @app.call(env)
end
......@@ -64,7 +65,7 @@ module Gitlab
var iframe = document.createElement('IFRAME');
iframe.setAttribute('id', 'speedscope-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);
</script>
</body>
......@@ -73,6 +74,17 @@ module Gitlab
[200, headers, [html]]
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
......@@ -75,20 +75,20 @@ module Gitlab
current_timeout_s = nil
else
mode = ENV['STACKPROF_MODE']&.to_sym || DEFAULT_MODE
interval = ENV['STACKPROF_INTERVAL']&.to_i
interval ||= (mode == :object ? DEFAULT_INTERVAL_EVENTS : DEFAULT_INTERVAL_US)
stackprof_interval = ENV['STACKPROF_INTERVAL']&.to_i
stackprof_interval ||= interval(mode)
log_event(
'starting profile',
profile_mode: mode,
profile_interval: interval,
profile_interval: stackprof_interval,
profile_timeout: timeout_s
)
::StackProf.start(
mode: mode,
raw: Gitlab::Utils.to_boolean(ENV['STACKPROF_RAW'] || 'true'),
interval: interval
interval: stackprof_interval
)
current_timeout_s = timeout_s
end
......@@ -131,5 +131,9 @@ module Gitlab
pid: Process.pid
}.merge(labels.compact))
end
def self.interval(mode)
mode == :object ? DEFAULT_INTERVAL_EVENTS : DEFAULT_INTERVAL_US
end
end
end
......@@ -24649,7 +24649,7 @@ msgstr ""
msgid "PerformanceBar|First Contentful Paint"
msgstr ""
msgid "PerformanceBar|Flamegraph"
msgid "PerformanceBar|Flamegraph with mode:"
msgstr ""
msgid "PerformanceBar|Frontend resources"
......@@ -24685,6 +24685,15 @@ msgstr ""
msgid "PerformanceBar|Trace"
msgstr ""
msgid "PerformanceBar|cpu"
msgstr ""
msgid "PerformanceBar|object"
msgstr ""
msgid "PerformanceBar|wall"
msgstr ""
msgid "Period in seconds"
msgstr ""
......
......@@ -46,7 +46,7 @@ RSpec.describe Gitlab::Middleware::Speedscope do
allow(env).to receive(:[]).with('warden').and_return(double('Warden', user: create(:admin)))
end
it 'runs StackProf and returns a flamegraph' do
it 'returns a flamegraph' do
expect(StackProf).to receive(:run).and_call_original
status, headers, body = middleware.call(env)
......@@ -55,6 +55,56 @@ RSpec.describe Gitlab::Middleware::Speedscope do
expect(headers).to eq({ 'Content-Type' => 'text/html' })
expect(body.first).to include('speedscope-iframe')
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
......
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