diff --git a/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-12H.benchmark.tsx b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-12H.benchmark.tsx new file mode 100644 index 00000000000..c851933a41a --- /dev/null +++ b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-12H.benchmark.tsx @@ -0,0 +1,39 @@ +// Copyright 2022 The Parca Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react'; +import ProfileIcicleGraph from '..'; +import {Provider} from 'react-redux'; +import {store} from '@parca/store'; +import parcaGraphData from './benchdata/parca-12h.json'; +import {Flamegraph} from '@parca/client'; + +const {store: reduxStore} = store(); + +const parcaGraph = parcaGraphData as Flamegraph; + +export default function ({callback = () => {}}): React.ReactElement { + return ( +
+ + {}} + /> + +
+ ); +} diff --git a/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-15M.benchmark.tsx b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-15M.benchmark.tsx new file mode 100644 index 00000000000..7b2465dfaad --- /dev/null +++ b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-15M.benchmark.tsx @@ -0,0 +1,39 @@ +// Copyright 2022 The Parca Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react'; +import ProfileIcicleGraph from '..'; +import {Provider} from 'react-redux'; +import {store} from '@parca/store'; +import parcaGraphData from './benchdata/parca-15m.json'; +import {Flamegraph} from '@parca/client'; + +const {store: reduxStore} = store(); + +const parcaGraph = parcaGraphData as Flamegraph; + +export default function ({callback = () => {}}): React.ReactElement { + return ( +
+ + {}} + /> + +
+ ); +} diff --git a/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-10M.benchmark.tsx b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-1D.benchmark.tsx similarity index 84% rename from ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-10M.benchmark.tsx rename to ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-1D.benchmark.tsx index bbb61a9b301..e0ee5cc03d6 100644 --- a/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-10M.benchmark.tsx +++ b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-1D.benchmark.tsx @@ -15,20 +15,21 @@ import React from 'react'; import ProfileIcicleGraph from '..'; import {Provider} from 'react-redux'; import {store} from '@parca/store'; -import parca10mGraphData from './benchdata/parca-10m.json'; +import parcaGraphData from './benchdata/parca-1d.json'; import {Flamegraph} from '@parca/client'; const {store: reduxStore} = store(); -const parca10mGraph = parca10mGraphData as Flamegraph; +const parcaGraph = parcaGraphData as Flamegraph; export default function ({callback = () => {}}): React.ReactElement { return (
{}} /> diff --git a/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-20M.benchmark.tsx b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-1H.benchmark.tsx similarity index 84% rename from ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-20M.benchmark.tsx rename to ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-1H.benchmark.tsx index 4fd9f53649b..f81960fd40e 100644 --- a/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-20M.benchmark.tsx +++ b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-1H.benchmark.tsx @@ -15,20 +15,21 @@ import React from 'react'; import ProfileIcicleGraph from '..'; import {Provider} from 'react-redux'; import {store} from '@parca/store'; -import parca20mGraphData from './benchdata/parca-20m.json'; +import parcaGraphData from './benchdata/parca-1h.json'; import {Flamegraph} from '@parca/client'; const {store: reduxStore} = store(); -const parca20mGraph = parca20mGraphData as Flamegraph; +const parcaGraph = parcaGraphData as Flamegraph; export default function ({callback = () => {}}): React.ReactElement { return (
{}} /> diff --git a/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-1M.benchmark.tsx b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-3D.benchmark.tsx similarity index 81% rename from ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-1M.benchmark.tsx rename to ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-3D.benchmark.tsx index 4ea1ad8a6e6..1b6e8da9ebd 100644 --- a/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-1M.benchmark.tsx +++ b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-3D.benchmark.tsx @@ -12,23 +12,24 @@ // limitations under the License. import React from 'react'; -import ProfileIcicleGraph from '../'; +import ProfileIcicleGraph from '..'; import {Provider} from 'react-redux'; import {store} from '@parca/store'; +import parcaGraphData from './benchdata/parca-3d.json'; import {Flamegraph} from '@parca/client'; -import parca1mGraphData from './benchdata/parca-1m.json'; const {store: reduxStore} = store(); -const parca1mGraph = parca1mGraphData as Flamegraph; +const parcaGraph = parcaGraphData as Flamegraph; export default function ({callback = () => {}}): React.ReactElement { return (
{}} /> diff --git a/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-6H.benchmark.tsx b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-6H.benchmark.tsx new file mode 100644 index 00000000000..3a1f68198d9 --- /dev/null +++ b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/ProfileIcicleGraph-6H.benchmark.tsx @@ -0,0 +1,39 @@ +// Copyright 2022 The Parca Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react'; +import ProfileIcicleGraph from '..'; +import {Provider} from 'react-redux'; +import {store} from '@parca/store'; +import parcaGraphData from './benchdata/parca-6h.json'; +import {Flamegraph} from '@parca/client'; + +const {store: reduxStore} = store(); + +const parcaGraph = parcaGraphData as Flamegraph; + +export default function ({callback = () => {}}): React.ReactElement { + return ( +
+ + {}} + /> + +
+ ); +} diff --git a/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/benchdata/common.js b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/benchdata/common.js new file mode 100644 index 00000000000..b26e83dd106 --- /dev/null +++ b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/benchdata/common.js @@ -0,0 +1,12 @@ +import commandLineArgs from 'command-line-args'; + +const optionDefinitions = [{name: 'apiEndpoint', type: String}]; +const options = commandLineArgs(optionDefinitions); + +export const getApiEndPoint = () => { + return options.apiEndpoint ?? 'https://demo.parca.dev'; +}; + +export const getGrpcMetadata = () => { + return {meta: JSON.parse(process.env.GRPC_METADATA ?? '{}')}; +}; diff --git a/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/benchdata/populateData.js b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/benchdata/populateData.js index 006bf4fb8df..6bbb94f11a0 100644 --- a/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/benchdata/populateData.js +++ b/ui/packages/shared/profile/src/ProfileIcicleGraph/benchmarks/benchdata/populateData.js @@ -17,12 +17,13 @@ const fs = require('fs-extra'); const path = require('path'); // const {fileURLToPath} = require('url'); const fetch = require('node-fetch'); +const {getApiEndPoint, getGrpcMetadata} = require('./common'); globalThis.fetch = fetch; globalThis.Headers = fetch.Headers; const DIR_NAME = __dirname; // path.dirname(fileURLToPath(import.meta.url)); -const apiEndpoint = 'https://demo.parca.dev'; +const apiEndpoint = getApiEndPoint(); const queryClient = new client.QueryServiceClient( new GrpcWebFetchTransport({ @@ -35,18 +36,21 @@ const populateDataIfNeeded = async (from, filename) => { if (Object.keys(await readFile(filePath)).length > 0) { return; } - const {response} = await queryClient.query({ - options: { - oneofKind: 'merge', - merge: { - start: client.Timestamp.fromDate(from), - end: client.Timestamp.fromDate(new Date()), - query: 'parca_agent_cpu:samples:count:cpu:nanoseconds:delta{container="parca"}', + const {response} = await queryClient.query( + { + options: { + oneofKind: 'merge', + merge: { + start: client.Timestamp.fromDate(from), + end: client.Timestamp.fromDate(new Date()), + query: 'parca_agent_cpu:samples:count:cpu:nanoseconds:delta{}', + }, }, + reportType: client.QueryRequest_ReportType.FLAMEGRAPH_TABLE, + mode: client.QueryRequest_Mode.MERGE, }, - reportType: client.QueryRequest_ReportType.FLAMEGRAPH_TABLE, - mode: client.QueryRequest_Mode.MERGE, - }); + getGrpcMetadata() + ); if (response.report.oneofKind !== 'flamegraph') { throw new Error('Expected flamegraph report'); } @@ -68,9 +72,12 @@ const readFile = async filename => { const run = async () => { await Promise.all([ - populateDataIfNeeded(new Date(new Date().getTime() - 1000 * 60), 'parca-1m.json'), - populateDataIfNeeded(new Date(new Date().getTime() - 1000 * 60 * 10), 'parca-10m.json'), - populateDataIfNeeded(new Date(new Date().getTime() - 1000 * 60 * 20), 'parca-20m.json'), + populateDataIfNeeded(new Date(new Date().getTime() - 1000 * 60 * 15), 'parca-15m.json'), + populateDataIfNeeded(new Date(new Date().getTime() - 1000 * 60 * 60), 'parca-1h.json'), + populateDataIfNeeded(new Date(new Date().getTime() - 1000 * 60 * 60 * 6), 'parca-6h.json'), + populateDataIfNeeded(new Date(new Date().getTime() - 1000 * 60 * 60 * 12), 'parca-12h.json'), + populateDataIfNeeded(new Date(new Date().getTime() - 1000 * 60 * 60 * 24), 'parca-1d.json'), + populateDataIfNeeded(new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * 3), 'parca-3d.json'), ]); }; diff --git a/ui/packages/shared/profile/src/TopTable/benchmarks/benchdata/populateData.js b/ui/packages/shared/profile/src/TopTable/benchmarks/benchdata/populateData.js index b65d46b8924..fec577c4771 100644 --- a/ui/packages/shared/profile/src/TopTable/benchmarks/benchdata/populateData.js +++ b/ui/packages/shared/profile/src/TopTable/benchmarks/benchdata/populateData.js @@ -17,12 +17,16 @@ const fs = require('fs-extra'); const path = require('path'); // const {fileURLToPath} = require('url'); const fetch = require('node-fetch'); +const { + getApiEndPoint, + getGrpcMetadata, +} = require('../../../ProfileIcicleGraph/benchmarks/benchdata/common'); globalThis.fetch = fetch; globalThis.Headers = fetch.Headers; const DIR_NAME = __dirname; // path.dirname(fileURLToPath(import.meta.url)); -const apiEndpoint = 'https://demo.parca.dev'; +const apiEndpoint = getApiEndPoint(); const queryClient = new client.QueryServiceClient( new GrpcWebFetchTransport({ @@ -35,18 +39,21 @@ const populateDataIfNeeded = async (from, filename) => { if (Object.keys(await readFile(filePath)).length > 0) { return; } - const {response} = await queryClient.query({ - options: { - oneofKind: 'merge', - merge: { - start: client.Timestamp.fromDate(from), - end: client.Timestamp.fromDate(new Date()), - query: 'parca_agent_cpu:samples:count:cpu:nanoseconds:delta{container="parca"}', + const {response} = await queryClient.query( + { + options: { + oneofKind: 'merge', + merge: { + start: client.Timestamp.fromDate(from), + end: client.Timestamp.fromDate(new Date()), + query: 'parca_agent_cpu:samples:count:cpu:nanoseconds:delta{container="parca"}', + }, }, + reportType: client.QueryRequest_ReportType.TOP, + mode: client.QueryRequest_Mode.MERGE, }, - reportType: client.QueryRequest_ReportType.TOP, - mode: client.QueryRequest_Mode.MERGE, - }); + getGrpcMetadata() + ); if (response.report.oneofKind !== 'top') { throw new Error('Expected topTable report'); } diff --git a/ui/scripts/README.md b/ui/scripts/README.md new file mode 100644 index 00000000000..152db59f101 --- /dev/null +++ b/ui/scripts/README.md @@ -0,0 +1,42 @@ +# Benchmark Runner + +It is a simple benchmark tool that loads the given React component in the browser and measures the time it takes to render the component. It also has support for running dataPopulation scripts that can be used to populate the data from the remote api that the components need to render. + +## Usage + +```bash +$ yarn benchmark +``` + +## Options + +```bash +yarn benchmark --pattern "ProfileIcicleGraph*" +``` + +To run only the benchmarks that match the given pattern. + +```bash +yarn benchmark --pattern "ProfileIcicleGraph*" --name flamegraphBefore +``` + +To run only the benchmarks that match the given pattern and save the result with the given name. + +```bash +yarn benchmark --pattern "ProfileIcicleGraph*" --compare "flamegraphBefore" +``` + +To run only the benchmarks that match the given pattern and compare them to the given result name. + +```bash +yarn benchmark --apiEndpoint='https://api.example.com' +``` + +To run the dataPopulation script against a different API endpoint. + +```bash +export GRPC_METADATA='{"key":"value","authorization":"Bearer ...."}' +yarn benchmark --apiEndpoint='https://api.example.com' +``` + +To run the dataPopulation script against a different API endpoint with custom headers. diff --git a/ui/scripts/run-benchmark.mts b/ui/scripts/run-benchmark.mts index b9a61efa5df..fd959bab4b5 100644 --- a/ui/scripts/run-benchmark.mts +++ b/ui/scripts/run-benchmark.mts @@ -34,6 +34,7 @@ const optionDefinitions = [ {name: 'debug', alias: 'd', type: Boolean}, {name: 'compare', alias: 'c', type: String}, {name: 'pattern', alias: 'p', type: String, defaultValue: '*'}, + {name: 'apiEndpoint', type: String}, ]; const options = commandLineArgs(optionDefinitions); const IS_DEBUG = process.env.DEBUG === 'true' || options.debug === true; @@ -163,11 +164,15 @@ const populateBenchmarkData = async (): Promise => { try { stopwatch.start(); spinner.start(`Running data population script: ${file}`); - const {stdout} = await execa('babel-node', [ - '--config-file', - path.join(DIR_NAME, '../babel.config.cjs'), - path.join(DIR_NAME, `../${file}`), - ]); + const {stdout} = await execa( + 'babel-node', + [ + '--config-file', + path.join(DIR_NAME, '../babel.config.cjs'), + path.join(DIR_NAME, `../${file}`), + options.apiEndpoint != null ? `--apiEndpoint=${options.apiEndpoint}` : '', + ].filter(Boolean) + ); console.log('stdout', stdout); spinner.succeed(`Data population script: ${file} completed ${stopwatch.stopAndReset()}ms`); } catch (error) {