From 6213fa385cb1db776cf2a9a3fc448da87f96c2ba Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 26 Aug 2024 20:58:40 +0200 Subject: [PATCH] Setup performance logging service --- .../account-balance.service.ts | 2 + apps/api/src/app/order/order.service.ts | 2 + .../calculator/portfolio-calculator.ts | 4 + .../src/app/portfolio/portfolio.controller.ts | 2 + .../api/src/app/portfolio/portfolio.module.ts | 2 + .../performance-logging.interceptor.ts | 80 +++++++++++++++++++ .../performance-logging.module.ts | 10 +++ .../performance-logging.service.ts | 21 +++++ .../exchange-rate-data.service.ts | 2 + 9 files changed, 125 insertions(+) create mode 100644 apps/api/src/interceptors/performance-logging/performance-logging.interceptor.ts create mode 100644 apps/api/src/interceptors/performance-logging/performance-logging.module.ts create mode 100644 apps/api/src/interceptors/performance-logging/performance-logging.service.ts diff --git a/apps/api/src/app/account-balance/account-balance.service.ts b/apps/api/src/app/account-balance/account-balance.service.ts index 65393cec8..244b4c684 100644 --- a/apps/api/src/app/account-balance/account-balance.service.ts +++ b/apps/api/src/app/account-balance/account-balance.service.ts @@ -1,4 +1,5 @@ import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event'; +import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { resetHours } from '@ghostfolio/common/helper'; @@ -90,6 +91,7 @@ export class AccountBalanceService { return accountBalance; } + @LogPerformance public async getAccountBalances({ filters, user, diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index f66380e1f..d9ff68d61 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -1,5 +1,6 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event'; +import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/data-gathering.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; @@ -519,6 +520,7 @@ export class OrderService { return { activities, count }; } + @LogPerformance public async getOrdersForPortfolioCalculator({ filters, userCurrency, diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 99f71ef0e..6493c0005 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -5,6 +5,7 @@ import { TransactionPointSymbol } from '@ghostfolio/api/app/portfolio/interfaces import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; +import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; @@ -148,6 +149,7 @@ export abstract class PortfolioCalculator { positions: TimelinePosition[] ): PortfolioSnapshot; + @LogPerformance private async computeSnapshot(): Promise { const lastTransactionPoint = last(this.transactionPoints); @@ -861,6 +863,7 @@ export abstract class PortfolioCalculator { return chartDateMap; } + @LogPerformance private computeTransactionPoints() { this.transactionPoints = []; const symbols: { [symbol: string]: TransactionPointSymbol } = {}; @@ -999,6 +1002,7 @@ export abstract class PortfolioCalculator { } } + @LogPerformance private async initialize() { const startTimeTotal = performance.now(); diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 7ce0b0847..036e48901 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -7,6 +7,7 @@ import { hasNotDefinedValuesInObject, nullifyValuesInObject } from '@ghostfolio/api/helper/object.helper'; +import { PerformanceLoggingInterceptor } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; @@ -390,6 +391,7 @@ export class PortfolioController { @Get('performance') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + @UseInterceptors(PerformanceLoggingInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) @Version('2') public async getPerformanceV2( diff --git a/apps/api/src/app/portfolio/portfolio.module.ts b/apps/api/src/app/portfolio/portfolio.module.ts index 7f1f375b1..ad81e9e15 100644 --- a/apps/api/src/app/portfolio/portfolio.module.ts +++ b/apps/api/src/app/portfolio/portfolio.module.ts @@ -4,6 +4,7 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { OrderModule } from '@ghostfolio/api/app/order/order.module'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { UserModule } from '@ghostfolio/api/app/user/user.module'; +import { PerformanceLoggingModule } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.module'; import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module'; import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module'; import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; @@ -38,6 +39,7 @@ import { RulesService } from './rules.service'; ImpersonationModule, MarketDataModule, OrderModule, + PerformanceLoggingModule, PrismaModule, RedactValuesInResponseModule, RedisCacheModule, diff --git a/apps/api/src/interceptors/performance-logging/performance-logging.interceptor.ts b/apps/api/src/interceptors/performance-logging/performance-logging.interceptor.ts new file mode 100644 index 000000000..d863f0ec3 --- /dev/null +++ b/apps/api/src/interceptors/performance-logging/performance-logging.interceptor.ts @@ -0,0 +1,80 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; + +import { PerformanceLoggingService } from './performance-logging.service'; + +@Injectable() +export class PerformanceLoggingInterceptor implements NestInterceptor { + public constructor( + private readonly performanceLoggingService: PerformanceLoggingService + ) {} + + public intercept( + context: ExecutionContext, + next: CallHandler + ): Observable { + const startTime = performance.now(); + + const className = context.getClass().name; + const methodName = context.getHandler().name; + + return next.handle().pipe( + tap(() => { + return this.performanceLoggingService.logPerformance({ + className, + methodName, + startTime + }); + }) + ); + } +} + +export function LogPerformance( + target: any, + propertyKey: string, + descriptor: PropertyDescriptor +) { + const originalMethod = descriptor.value; + + descriptor.value = async function (...args: any[]) { + const startTime = performance.now(); + const performanceLoggingService = new PerformanceLoggingService(); + + const result = originalMethod.apply(this, args); + + if (result instanceof Promise) { + // Handle async method + return result + .then((res: any) => { + performanceLoggingService.logPerformance({ + startTime, + className: target.constructor.name, + methodName: propertyKey + }); + + return res; + }) + .catch((error: any) => { + throw error; + }); + } else { + // Handle sync method + performanceLoggingService.logPerformance({ + startTime, + className: target.constructor.name, + methodName: propertyKey + }); + + return result; + } + }; + + return descriptor; +} diff --git a/apps/api/src/interceptors/performance-logging/performance-logging.module.ts b/apps/api/src/interceptors/performance-logging/performance-logging.module.ts new file mode 100644 index 000000000..a26b381e5 --- /dev/null +++ b/apps/api/src/interceptors/performance-logging/performance-logging.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; + +import { PerformanceLoggingInterceptor } from './performance-logging.interceptor'; +import { PerformanceLoggingService } from './performance-logging.service'; + +@Module({ + exports: [PerformanceLoggingInterceptor, PerformanceLoggingService], + providers: [PerformanceLoggingInterceptor, PerformanceLoggingService] +}) +export class PerformanceLoggingModule {} diff --git a/apps/api/src/interceptors/performance-logging/performance-logging.service.ts b/apps/api/src/interceptors/performance-logging/performance-logging.service.ts new file mode 100644 index 000000000..1b1faf8e0 --- /dev/null +++ b/apps/api/src/interceptors/performance-logging/performance-logging.service.ts @@ -0,0 +1,21 @@ +import { Injectable, Logger } from '@nestjs/common'; + +@Injectable() +export class PerformanceLoggingService { + public logPerformance({ + className, + methodName, + startTime + }: { + className: string; + methodName: string; + startTime: number; + }) { + const endTime = performance.now(); + + Logger.debug( + `Completed execution of ${methodName}() in ${((endTime - startTime) / 1000).toFixed(3)} seconds`, + className + ); + } +} diff --git a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts index 1f08034cd..31b2f885c 100644 --- a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts +++ b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts @@ -1,3 +1,4 @@ +import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; @@ -46,6 +47,7 @@ export class ExchangeRateDataService { return this.currencyPairs; } + @LogPerformance public async getExchangeRatesByCurrency({ currencies, endDate = new Date(),