Feature/set up a performance logging service (#3703)

* Setup performance logging service

* Update changelog
pull/3706/head
Thomas Kaul 4 months ago committed by GitHub
parent 4505441691
commit c4a28c6bff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## 2.106.0-beta.2 - 2024-08-26 ## 2.106.0-beta.2 - 2024-08-26
### Added
- Set up a performance logging service
### Changed ### Changed
- Reworked the portfolio calculator - Reworked the portfolio calculator

@ -1,4 +1,5 @@
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event'; 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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { resetHours } from '@ghostfolio/common/helper'; import { resetHours } from '@ghostfolio/common/helper';
@ -90,6 +91,7 @@ export class AccountBalanceService {
return accountBalance; return accountBalance;
} }
@LogPerformance
public async getAccountBalances({ public async getAccountBalances({
filters, filters,
user, user,

@ -1,5 +1,6 @@
import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event'; 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 { DataGatheringService } from '@ghostfolio/api/services/data-gathering/data-gathering.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
@ -519,6 +520,7 @@ export class OrderService {
return { activities, count }; return { activities, count };
} }
@LogPerformance
public async getOrdersForPortfolioCalculator({ public async getOrdersForPortfolioCalculator({
filters, filters,
userCurrency, userCurrency,

@ -5,6 +5,7 @@ import { TransactionPointSymbol } from '@ghostfolio/api/app/portfolio/interfaces
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface'; import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; 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 { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
@ -148,6 +149,7 @@ export abstract class PortfolioCalculator {
positions: TimelinePosition[] positions: TimelinePosition[]
): PortfolioSnapshot; ): PortfolioSnapshot;
@LogPerformance
private async computeSnapshot(): Promise<PortfolioSnapshot> { private async computeSnapshot(): Promise<PortfolioSnapshot> {
const lastTransactionPoint = last(this.transactionPoints); const lastTransactionPoint = last(this.transactionPoints);
@ -861,6 +863,7 @@ export abstract class PortfolioCalculator {
return chartDateMap; return chartDateMap;
} }
@LogPerformance
private computeTransactionPoints() { private computeTransactionPoints() {
this.transactionPoints = []; this.transactionPoints = [];
const symbols: { [symbol: string]: TransactionPointSymbol } = {}; const symbols: { [symbol: string]: TransactionPointSymbol } = {};
@ -999,6 +1002,7 @@ export abstract class PortfolioCalculator {
} }
} }
@LogPerformance
private async initialize() { private async initialize() {
const startTimeTotal = performance.now(); const startTimeTotal = performance.now();
@ -1033,14 +1037,6 @@ export abstract class PortfolioCalculator {
JSON.stringify(this.snapshot), JSON.stringify(this.snapshot),
this.configurationService.get('CACHE_QUOTES_TTL') this.configurationService.get('CACHE_QUOTES_TTL')
); );
Logger.debug(
`Computed portfolio snapshot in ${(
(performance.now() - startTimeTotal) /
1000
).toFixed(3)} seconds`,
'PortfolioCalculator'
);
} }
} }
} }

@ -1,4 +1,5 @@
import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { resetHours } from '@ghostfolio/common/helper'; import { resetHours } from '@ghostfolio/common/helper';
@ -27,6 +28,7 @@ export class CurrentRateService {
@Inject(REQUEST) private readonly request: RequestWithUser @Inject(REQUEST) private readonly request: RequestWithUser
) {} ) {}
@LogPerformance
// TODO: Pass user instead of using this.request.user // TODO: Pass user instead of using this.request.user
public async getValues({ public async getValues({
dataGatheringItems, dataGatheringItems,

@ -7,6 +7,7 @@ import {
hasNotDefinedValuesInObject, hasNotDefinedValuesInObject,
nullifyValuesInObject nullifyValuesInObject
} from '@ghostfolio/api/helper/object.helper'; } 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 { 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 { 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'; 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') @Get('performance')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(PerformanceLoggingInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor)
@Version('2') @Version('2')
public async getPerformanceV2( public async getPerformanceV2(

@ -4,6 +4,7 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { OrderModule } from '@ghostfolio/api/app/order/order.module'; import { OrderModule } from '@ghostfolio/api/app/order/order.module';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { UserModule } from '@ghostfolio/api/app/user/user.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 { 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 { 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'; 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, ImpersonationModule,
MarketDataModule, MarketDataModule,
OrderModule, OrderModule,
PerformanceLoggingModule,
PrismaModule, PrismaModule,
RedactValuesInResponseModule, RedactValuesInResponseModule,
RedisCacheModule, RedisCacheModule,

@ -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<any> {
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;
}

@ -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 {}

@ -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
);
}
}

@ -206,9 +206,9 @@ export class CoinGeckoService implements DataProviderInterface {
let message = error; let message = error;
if (error?.code === 'ABORT_ERR') { if (error?.code === 'ABORT_ERR') {
message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${this.configurationService.get( message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
'REQUEST_TIMEOUT' this.configurationService.get('REQUEST_TIMEOUT') / 1000
)}ms`; ).toFixed(3)} seconds`;
} }
Logger.error(message, 'CoinGeckoService'); Logger.error(message, 'CoinGeckoService');

@ -290,9 +290,9 @@ export class EodHistoricalDataService implements DataProviderInterface {
let message = error; let message = error;
if (error?.code === 'ABORT_ERR') { if (error?.code === 'ABORT_ERR') {
message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${this.configurationService.get( message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
'REQUEST_TIMEOUT' this.configurationService.get('REQUEST_TIMEOUT') / 1000
)}ms`; ).toFixed(3)} seconds`;
} }
Logger.error(message, 'EodHistoricalDataService'); Logger.error(message, 'EodHistoricalDataService');

@ -154,9 +154,9 @@ export class FinancialModelingPrepService implements DataProviderInterface {
let message = error; let message = error;
if (error?.code === 'ABORT_ERR') { if (error?.code === 'ABORT_ERR') {
message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${this.configurationService.get( message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
'REQUEST_TIMEOUT' this.configurationService.get('REQUEST_TIMEOUT') / 1000
)}ms`; ).toFixed(3)} seconds`;
} }
Logger.error(message, 'FinancialModelingPrepService'); Logger.error(message, 'FinancialModelingPrepService');

@ -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 { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
@ -46,6 +47,7 @@ export class ExchangeRateDataService {
return this.currencyPairs; return this.currencyPairs;
} }
@LogPerformance
public async getExchangeRatesByCurrency({ public async getExchangeRatesByCurrency({
currencies, currencies,
endDate = new Date(), endDate = new Date(),

Loading…
Cancel
Save