Feature/set up a performance logging service (#3703)
* Setup performance logging service * Update changelog
This commit is contained in:
parent
4505441691
commit
c4a28c6bff
@ -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…
x
Reference in New Issue
Block a user