Skip to content

Commit

Permalink
ui: add end-to-end request/response via grcp-web
Browse files Browse the repository at this point in the history
fixes #8
  • Loading branch information
willpoint committed Dec 15, 2022
1 parent 2b59fc2 commit 3bc4b27
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 46 deletions.
15 changes: 15 additions & 0 deletions ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,8 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/google-protobuf": "^3.15.6"
}
}
24 changes: 19 additions & 5 deletions ui/src/components/inputs/Inputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum FieldType {
LIST_BASED = 'list-based-field',
OBJECT_BASED = 'object-based-field',
SINGLE_SELECTION_BASED = 'single-selection-field',
TIME_BASED = 'time-based-field' // append 's' to the output representing duration in seconds
};

export type FormField = {
Expand All @@ -20,7 +21,7 @@ export type FormField = {
fieldType: FieldType,
keys?: string[],
required?: string | boolean;
options?: { name: string, value: string, info?: string }[],
options?: { name: string, value: string|number, info?: string }[],
};

interface Props {
Expand Down Expand Up @@ -50,7 +51,7 @@ export default function Inputs(props: Props) {

const { control, handleSubmit } = useForm({ defaultValues });

const onSubmit = (data: any) => {
const onSubmit = (data: any) => {
handleFormSubmission?.(data);
};

Expand All @@ -64,7 +65,20 @@ export default function Inputs(props: Props) {
name={f.name}
control={control}
rules={{ required: f.required }}
render={({ field, fieldState: { error } }) => {
render={({ field, fieldState: { error } }) => {
if (f.fieldType === FieldType.TIME_BASED) {
return (
<TextField
variant='outlined'
label={f.label}
size='small'
type='number'
error={error !== undefined}
helperText={error !== undefined ? error.message : f.info}
{...field}
/>
);
}
if (f.fieldType === FieldType.SINGLE_SELECTION_BASED) {
return (
<TextField
Expand All @@ -84,7 +98,7 @@ export default function Inputs(props: Props) {
</MenuItem>
)}
</TextField>
)
);
}
return (
<TextField
Expand All @@ -95,7 +109,7 @@ export default function Inputs(props: Props) {
helperText={error !== undefined ? error.message : f.info}
{...field}
/>
)
);
}}
/>
</Grid>
Expand Down
28 changes: 15 additions & 13 deletions ui/src/components/load-tester/Fields.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { FieldType, FormField } from 'src/components/inputs/Inputs';

import {
RunLoadtestRequestBroadcastTxMethod as BroadcastMethod,
RunLoadtestRequestEndpointSelectMethod as SelectMethod,
} from 'src/gen/LoadtestApi';
import { RunLoadtestRequest } from 'src/gen/orijtech/cosmosloadtester/v1/loadtest_service_pb';

const BroadcastTxMethod = RunLoadtestRequest.BroadcastTxMethod;
const SelectMethod = RunLoadtestRequest.EndpointSelectMethod;

export const fields: FormField[] = [
{
name: 'broadcastTxMethod',
label: 'Broadcast TX Method',
info: 'The broadcast tx method to use when submitting transactions, can be async, sync, or commit',
fieldType: FieldType.SINGLE_SELECTION_BASED,
default: BroadcastMethod.BROADCAST_TX_METHOD_ASYNC,
default: BroadcastTxMethod.BROADCAST_TX_METHOD_ASYNC,
options: [
{ name: 'Async', value: BroadcastMethod.BROADCAST_TX_METHOD_ASYNC },
{ name: 'Sync', value: BroadcastMethod.BROADCAST_TX_METHOD_SYNC },
{ name: 'Commit', value: BroadcastMethod.BROADCAST_TX_METHOD_COMMIT },
{ name: 'Async', value: BroadcastTxMethod.BROADCAST_TX_METHOD_ASYNC },
{ name: 'Sync', value: BroadcastTxMethod.BROADCAST_TX_METHOD_SYNC },
{ name: 'Commit', value: BroadcastTxMethod.BROADCAST_TX_METHOD_COMMIT },
],
},
{
Expand All @@ -34,6 +34,7 @@ export const fields: FormField[] = [
name: 'clientFactory',
required: 'Client factory field is required.',
label: 'Client factory',
default: 'test-cosmos-client-factory',
info: 'The identifier of the client factory to use for generating load testing transactions. Maps to --client-factory in tm-load-test',
fieldType: FieldType.VALUE_BASED,
},
Expand All @@ -42,7 +43,8 @@ export const fields: FormField[] = [
label: 'Duration',
info: 'The duration (in seconds) for which to handle the load test. Maps to --time in tm-load-test.',
required: 'Duration to handle load test is required',
fieldType: FieldType.VALUE_BASED,
default: 20,
fieldType: FieldType.TIME_BASED,
},
{
name: 'endpoints',
Expand All @@ -69,15 +71,15 @@ export const fields: FormField[] = [
name: 'peerConnectTimeout',
label: 'Peer connect timeout',
info: 'The number of seconds to wait for all required peers to connect if expect-peers > 0. Maps to --peer-connect-timeout in tm-load-test.',
default: 2,
fieldType: FieldType.VALUE_BASED,
default: 20,
fieldType: FieldType.TIME_BASED,
},
{
name: 'sendPeriod',
label: 'Send period',
info: 'The period (in seconds) at which to send batches of transactions. Maps to --send-period in tm-load-test.',
default: 1,
fieldType: FieldType.VALUE_BASED,
default: 20,
fieldType: FieldType.TIME_BASED,
},
{
name: 'connectionCount',
Expand Down
68 changes: 49 additions & 19 deletions ui/src/components/load-tester/LoadTester.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,59 @@ import Typography from '@mui/material/Typography';
import Alert from '@mui/material/Alert';
import Button from '@mui/material/Button';

import { V1RunLoadtestResponse as LoadTestResult } from 'src/gen/LoadtestApi';
import {
LoadtestServiceClient,
} from 'src/gen/orijtech/cosmosloadtester/v1/Loadtest_serviceServiceClientPb';
import {
RunLoadtestRequest,
RunLoadtestResponse,
} from 'src/gen/orijtech/cosmosloadtester/v1/loadtest_service_pb';

import * as timestamp_pb from 'google-protobuf/google/protobuf/timestamp_pb';

import Inputs from 'src/components/inputs/Inputs';
import Outputs from 'src/components/output/Outputs';
import { fields } from './Fields';

const service = new LoadtestServiceClient('');

export default function LoadTester() {
const [running, setRunning] = React.useState(false);
const [error, setError] = React.useState('');
const [data, setData] = React.useState<LoadTestResult>();
const [data, setData] = React.useState<RunLoadtestResponse.AsObject>();

const submitRef = React.useRef<HTMLButtonElement>(null);

const onFormSubmit = async (data: any) => {
try {
// reset previous response & error after new form submission
setData(undefined);
setError('');

setRunning(true);
await new Promise((resolve) => {
setTimeout(() => {
resolve(data);
setData(data as LoadTestResult);
}, 2000);
});
const endpoints: string[] = data.endpoints?.split(',') || [];
const request = new RunLoadtestRequest();
request
.setBroadcastTxMethod(data.broadcastTxMethod)
.setClientFactory(data.clientFactory)
.setDuration(new timestamp_pb.Timestamp().setSeconds(data.duration))
.setEndpointSelectMethod(data.endpointSelectMethod)
.setEndpointsList(endpoints)
.setExpectPeersCount(data.expectPeersCount)
.setMaxEndpointCount(data.maxEndpointCount)
.setMinPeerConnectivityCount(data.minPeerConnectivityCount)
.setPeerConnectTimeout(new timestamp_pb.Timestamp().setSeconds(data.peerConnectTimeout))
.setSendPeriod(new timestamp_pb.Timestamp().setSeconds(data.sendPeriod))
.setStatsOutputFilePath(data.statsOutputFilePath)
.setTransactionCount(data.transactionCount)
.setTransactionSizeBytes(data.transactionSizeBytes)
.setTransactionsPerSecond(data.transactionsPerSecond);

const result = await service.runLoadtest(request, null);
setData(result.toObject());
console.log(result.toObject());
} catch (e: any) {
console.log(e);
setError(e.message);
} finally {
setRunning(false);
Expand Down Expand Up @@ -57,18 +87,18 @@ export default function LoadTester() {
</Button>

{/* display errors if any occured */}
{error !== '' && <Typography variant='caption'>{error}</Typography>}
{error !== '' && <Typography component='h6' variant='caption'>{error}</Typography>}
{/* render data if it exists */}
{
data !== undefined &&
<>
<Alert severity='success' sx={{ my: 3 }}>
<Typography variant='caption'>
Load Testing Results
</Typography>
</Alert>
<Outputs data={data} />
</>
data !== undefined &&
<>
<Alert severity='success' sx={{ my: 3 }}>
<Typography variant='caption'>
Load Testing Results
</Typography>
</Alert>
<Outputs data={data} />
</>
}
</Paper>
);
Expand Down
36 changes: 27 additions & 9 deletions ui/src/components/output/Outputs.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import Chip from '@mui/material/Chip';

import { V1RunLoadtestResponse as Result } from 'src/gen/LoadtestApi';
import {
RunLoadtestResponse as Result,
} from 'src/gen/orijtech/cosmosloadtester/v1/loadtest_service_pb';

interface OutputProps {
data: Result;
data: Result.AsObject;
};

interface ResultBarProps {
title: string;
value: string | number;
};

function ResultBar(props: ResultBarProps) {
const { title, value } = props;
return (
<span style={{ marginRight: 5 }}>
<Chip label={title} size='small' variant='outlined' color='info' sx={{ borderRadius: 0 }} />
<Chip label={value} size='small' variant='filled' color='info' sx={{ borderRadius: 0 }} />
</span>
);
}

export default function Inputs(props: OutputProps) {
const { data } = props;
return (
<Box>
<Typography variant='caption'>
<pre style={{ whiteSpace: 'pre-line', wordWrap: 'break-word' }}>
{JSON.stringify(props.data, null, 2)}
</pre>
</Typography>
<ResultBar title='avg bytes per second' value={data.avgBytesPerSecond} />
<ResultBar title='avg tx per second' value={data.avgTxsPerSecond} />
<ResultBar title='total bytes' value={data.totalBytes} />
<ResultBar title='total time' value={`${data.totalTime?.seconds || 0} seconds`} />
<ResultBar title='total tx' value={data.totalTxs} />
</Box>
)
);
}

0 comments on commit 3bc4b27

Please sign in to comment.