Skip to content

Commit 3917d2e

Browse files
authored
Merge pull request #1347 from RedisInsight/feature/RI-3751_command_execution_time
#RI-3751-add command execution time
2 parents 90badf2 + 3956428 commit 3917d2e

File tree

22 files changed

+1443
-1067
lines changed

22 files changed

+1443
-1067
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
3+
export class workbenchExecutionTime1667368983699 implements MigrationInterface {
4+
name = 'workbenchExecutionTime1667368983699'
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(`DROP INDEX "IDX_5cd90dd6def1fd7c521e53fb2c"`);
8+
await queryRunner.query(`CREATE TABLE "temporary_command_execution" ("id" varchar PRIMARY KEY NOT NULL, "databaseId" varchar NOT NULL, "command" text NOT NULL, "result" text NOT NULL, "role" varchar, "nodeOptions" varchar, "encryption" varchar, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "mode" varchar, "resultsMode" varchar, "summary" varchar, "executionTime" integer, CONSTRAINT "FK_ea8adfe9aceceb79212142206b8" FOREIGN KEY ("databaseId") REFERENCES "database_instance" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`);
9+
await queryRunner.query(`INSERT INTO "temporary_command_execution"("id", "databaseId", "command", "result", "role", "nodeOptions", "encryption", "createdAt", "mode", "resultsMode", "summary") SELECT "id", "databaseId", "command", "result", "role", "nodeOptions", "encryption", "createdAt", "mode", "resultsMode", "summary" FROM "command_execution"`);
10+
await queryRunner.query(`DROP TABLE "command_execution"`);
11+
await queryRunner.query(`ALTER TABLE "temporary_command_execution" RENAME TO "command_execution"`);
12+
await queryRunner.query(`CREATE INDEX "IDX_5cd90dd6def1fd7c521e53fb2c" ON "command_execution" ("createdAt") `);
13+
}
14+
15+
public async down(queryRunner: QueryRunner): Promise<void> {
16+
await queryRunner.query(`DROP INDEX "IDX_5cd90dd6def1fd7c521e53fb2c"`);
17+
await queryRunner.query(`ALTER TABLE "command_execution" RENAME TO "temporary_command_execution"`);
18+
await queryRunner.query(`CREATE TABLE "command_execution" ("id" varchar PRIMARY KEY NOT NULL, "databaseId" varchar NOT NULL, "command" text NOT NULL, "result" text NOT NULL, "role" varchar, "nodeOptions" varchar, "encryption" varchar, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "mode" varchar, "resultsMode" varchar, "summary" varchar, CONSTRAINT "FK_ea8adfe9aceceb79212142206b8" FOREIGN KEY ("databaseId") REFERENCES "database_instance" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`);
19+
await queryRunner.query(`INSERT INTO "command_execution"("id", "databaseId", "command", "result", "role", "nodeOptions", "encryption", "createdAt", "mode", "resultsMode", "summary") SELECT "id", "databaseId", "command", "result", "role", "nodeOptions", "encryption", "createdAt", "mode", "resultsMode", "summary" FROM "temporary_command_execution"`);
20+
await queryRunner.query(`DROP TABLE "temporary_command_execution"`);
21+
await queryRunner.query(`CREATE INDEX "IDX_5cd90dd6def1fd7c521e53fb2c" ON "command_execution" ("createdAt") `);
22+
}
23+
24+
}

redisinsight/api/migration/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { workbenchMode1660664717573 } from './1660664717573-workbench-mode';
1919
import { workbenchGroupMode1663093411715 } from './1663093411715-workbench-group-mode';
2020
import { databaseAnalysis1664785208236 } from './1664785208236-database-analysis';
2121
import { databaseAnalysisExpirationGroups1664886479051 } from './1664886479051-database-analysis-expiration-groups';
22+
import { workbenchExecutionTime1667368983699 } from './1667368983699-workbench-execution-time';
2223

2324
export default [
2425
initialMigration1614164490968,
@@ -42,4 +43,5 @@ export default [
4243
workbenchGroupMode1663093411715,
4344
databaseAnalysis1664785208236,
4445
databaseAnalysisExpirationGroups1664886479051,
46+
workbenchExecutionTime1667368983699,
4547
];

redisinsight/api/src/modules/workbench/dto/create-command-executions.dto.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
22
import {
3-
IsEnum, IsArray, IsNotEmptyObject, IsOptional, ArrayNotEmpty, ValidateNested,
3+
IsEnum, IsArray, IsNotEmptyObject, IsDefined, IsOptional, IsString, ArrayNotEmpty, ValidateNested,
44
} from 'class-validator';
55
import { Type } from 'class-transformer';
66
import { ClusterSingleNodeOptions } from 'src/modules/cli/dto/cli.dto';
@@ -9,11 +9,13 @@ import { ClusterNodeRole, RunQueryMode, ResultsMode } from './create-command-exe
99
export class CreateCommandExecutionsDto {
1010
@ApiProperty({
1111
isArray: true,
12+
type: String,
1213
description: 'Redis commands',
1314
})
1415
@IsArray()
1516
@ArrayNotEmpty()
16-
@Type(() => String)
17+
@IsDefined()
18+
@IsString({ each: true })
1719
commands: string[];
1820

1921
@ApiPropertyOptional({

redisinsight/api/src/modules/workbench/entities/command-execution.entity.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ export class CommandExecutionEntity {
7171
@Column({ nullable: true })
7272
encryption: string;
7373

74+
@Column({ nullable: true })
75+
executionTime?: number;
76+
7477
@CreateDateColumn()
7578
@Index()
7679
createdAt: Date;

redisinsight/api/src/modules/workbench/models/command-execution.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ export class CommandExecution {
110110
@Expose()
111111
createdAt: Date;
112112

113+
@ApiPropertyOptional({
114+
description: 'Workbench command execution time',
115+
type: Number,
116+
})
117+
@Expose()
118+
executionTime?: number;
119+
113120
constructor(partial: Partial<CommandExecution> = {}) {
114121
Object.assign(this, partial);
115122
}

redisinsight/api/src/modules/workbench/providers/command-execution.provider.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class CommandExecutionProvider {
3131
*/
3232
async createMany(commandExecutions: Partial<CommandExecution>[]): Promise<CommandExecution[]> {
3333
// todo: limit by 30 max to insert
34-
let entities = await Promise.all(commandExecutions.map(async (commandExecution, idx) => {
34+
let entities = await Promise.all(commandExecutions.map(async (commandExecution) => {
3535
const entity = plainToClass(CommandExecutionEntity, commandExecution);
3636

3737
// Do not store command execution result that exceeded limitation
@@ -61,6 +61,7 @@ export class CommandExecutionProvider {
6161
result: commandExecutions[idx].result,
6262
nodeOptions: commandExecutions[idx].nodeOptions,
6363
summary: commandExecutions[idx].summary,
64+
executionTime: commandExecutions[idx].executionTime,
6465
},
6566
)),
6667
);
@@ -95,6 +96,7 @@ export class CommandExecutionProvider {
9596
'e.mode',
9697
'e.summary',
9798
'e.resultsMode',
99+
'e.executionTime',
98100
])
99101
.orderBy('e.createdAt', 'DESC')
100102
.limit(WORKBENCH_CONFIG.maxItemsPerDb)

redisinsight/api/src/modules/workbench/workbench.service.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,10 @@ describe('WorkbenchService', () => {
136136

137137
describe('createCommandExecution', () => {
138138
it('should successfully execute command and save it', async () => {
139-
expect(await service.createCommandExecution(mockClientOptions, mockCreateCommandExecutionDto))
140-
.toEqual(mockCommandExecutionToRun);
139+
const result = await service.createCommandExecution(mockClientOptions, mockCreateCommandExecutionDto);
140+
// can't predict execution time
141+
expect(result).toMatchObject(mockCommandExecutionToRun);
142+
expect(result.executionTime).toBeGreaterThan(0);
141143
});
142144
it('should save result as unsupported command message', async () => {
143145
workbenchCommandsExecutor.sendCommand.mockResolvedValueOnce(mockCommandExecutionResults);

redisinsight/api/src/modules/workbench/workbench.service.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ export class WorkbenchService {
4646
},
4747
];
4848
} else {
49+
const startCommandExecutionTime = process.hrtime.bigint();
4950
commandExecution.result = await this.commandsExecutor.sendCommand(clientOptions, { ...dto, command });
51+
const endCommandExecutionTime = process.hrtime.bigint();
52+
commandExecution.executionTime = Math.round((Number(endCommandExecutionTime - startCommandExecutionTime) / 1000));
5053
}
5154

5255
return commandExecution;
@@ -67,6 +70,9 @@ export class WorkbenchService {
6770
...dto,
6871
databaseId: clientOptions.instanceId,
6972
};
73+
let executionTimeInNanoseconds = BigInt(0);
74+
75+
const startCommandExecutionTime = process.hrtime.bigint();
7076

7177
const executionResults = await Promise.all(commands.map(async (singleCommand) => {
7278
const command = multilineCommandToOneLine(singleCommand);
@@ -79,9 +85,16 @@ export class WorkbenchService {
7985
});
8086
}
8187
const result = await this.commandsExecutor.sendCommand(clientOptions, { ...dto, command });
88+
const endCommandExecutionTime = process.hrtime.bigint();
89+
90+
executionTimeInNanoseconds += (endCommandExecutionTime - startCommandExecutionTime);
8291
return ({ ...result[0], command });
8392
}));
8493

94+
if (Number(executionTimeInNanoseconds) !== 0) {
95+
commandExecution.executionTime = Math.round(Number(executionTimeInNanoseconds) / 1000);
96+
}
97+
8598
const successCommands = executionResults.filter(
8699
(command) => command.status === CommandExecutionStatus.Success,
87100
);

redisinsight/api/test/api/workbench/GET-instance-id-workbench-command_executions-id.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const responseSchema = Joi.object().keys({
3232
mode: Joi.string().required(),
3333
summary: Joi.string().allow(null),
3434
resultsMode: Joi.string().allow(null),
35+
executionTime: Joi.number().allow(null),
3536
nodeOptions: Joi.object().keys({
3637
host: Joi.string().required(),
3738
port: Joi.number().required(),

redisinsight/api/test/api/workbench/GET-instance-id-workbench-command_executions.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const responseSchema = Joi.array().items(Joi.object().keys({
2020
mode: Joi.string().required(),
2121
summary: Joi.string().allow(null),
2222
resultsMode: Joi.string().allow(null),
23+
executionTime: Joi.number().allow(null),
2324
nodeOptions: Joi.object().keys({
2425
host: Joi.string().required(),
2526
port: Joi.number().required(),

0 commit comments

Comments
 (0)