UNPKG

@chevre/domain

Version:

Chevre Domain Library for Node.js

443 lines (442 loc) 22.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TelemetryPurposeType = exports.TelemetryScope = void 0; exports.searchGlobalFlow = searchGlobalFlow; exports.searchGlobalStock = searchGlobalStock; exports.searchSellerFlow = searchSellerFlow; exports.searchSellerStock = searchSellerStock; exports.search = search; exports.createFlow = createFlow; exports.createStock = createStock; const createDebug = require("debug"); const moment = require("moment"); const factory = require("../../factory"); const debug = createDebug('chevre-domain:service'); const TELEMETRY_UNIT_OF_MEASUREMENT_IN_SECONDS = 60; // 測定単位時間(秒) var TelemetryScope; (function (TelemetryScope) { TelemetryScope["Global"] = "Global"; TelemetryScope["Seller"] = "Seller"; })(TelemetryScope || (exports.TelemetryScope = TelemetryScope = {})); var TelemetryPurposeType; (function (TelemetryPurposeType) { TelemetryPurposeType["Flow"] = "Flow"; TelemetryPurposeType["Stock"] = "Stock"; })(TelemetryPurposeType || (exports.TelemetryPurposeType = TelemetryPurposeType = {})); function searchGlobalFlow(searchConditions) { return search(Object.assign(Object.assign({}, searchConditions), { scope: TelemetryScope.Global, purpose: TelemetryPurposeType.Flow })); } function searchGlobalStock(searchConditions) { return search(Object.assign(Object.assign({}, searchConditions), { scope: TelemetryScope.Global, purpose: TelemetryPurposeType.Stock })); } function searchSellerFlow(searchConditions) { return search(Object.assign(Object.assign({}, searchConditions), { scope: TelemetryScope.Seller, purpose: TelemetryPurposeType.Flow })); } function searchSellerStock(searchConditions) { return search(Object.assign(Object.assign({}, searchConditions), { scope: TelemetryScope.Seller, purpose: TelemetryPurposeType.Stock })); } /** * 計測データを検索する * @param searchConditions.measuredFrom 計測日時from * @param searchConditions.measuredThrough 計測日時through */ function search(searchConditions) { return (repos) => __awaiter(this, void 0, void 0, function* () { return repos.telemetry.telemetryModel.find({ 'object.scope': { $exists: true, $eq: searchConditions.scope }, 'object.measuredAt': { $exists: true, $gte: searchConditions.measuredFrom, $lt: searchConditions.measuredThrough }, 'purpose.typeOf': { $exists: true, $eq: searchConditions.purpose } }) .sort({ 'object.measuredAt': 1 }) .lean() .exec(); }); } /** * フロー測定データを作成する */ // tslint:disable-next-line:no-single-line-block-comment /* istanbul ignore next */ function createFlow(target) { return (repos) => __awaiter(this, void 0, void 0, function* () { const startDate = new Date(); const measuredThrough = moment(target.measuredAt); const measuredFrom = moment(measuredThrough) .add(-TELEMETRY_UNIT_OF_MEASUREMENT_IN_SECONDS, 'seconds'); let telemetry; if (target.sellerId !== undefined) { const flowData = yield createSellerFlow(measuredFrom.toDate(), measuredThrough.toDate(), target.sellerId)({ transaction: repos.transaction, action: repos.action }); debug('flowData created.'); telemetry = { purpose: { typeOf: TelemetryPurposeType.Flow }, object: { scope: TelemetryScope.Seller, measuredAt: target.measuredAt, sellerId: target.sellerId }, result: flowData, startDate: startDate, endDate: new Date() }; } else { const flowData = yield createGlobalFlow(measuredFrom.toDate(), measuredThrough.toDate())({ task: repos.task }); debug('flowData created.'); telemetry = { purpose: { typeOf: TelemetryPurposeType.Flow }, object: { scope: TelemetryScope.Global, measuredAt: target.measuredAt }, result: flowData, startDate: startDate, endDate: new Date() }; } yield repos.telemetry.telemetryModel.create(telemetry); debug('telemetry saved.'); }); } /** * ストック測定データを作成する */ // tslint:disable-next-line:no-single-line-block-comment /* istanbul ignore next */ function createStock(target) { return (repos) => __awaiter(this, void 0, void 0, function* () { const startDate = new Date(); let telemetry; if (target.sellerId !== undefined) { const stockData = yield createSellerStock(target.measuredAt, target.sellerId)({ transaction: repos.transaction }); debug('stockData created.'); telemetry = { purpose: { typeOf: TelemetryPurposeType.Stock }, object: { scope: TelemetryScope.Seller, measuredAt: target.measuredAt, sellerId: target.sellerId }, result: stockData, startDate: startDate, endDate: new Date() }; } else { const stockData = yield createGlobalStock(target.measuredAt)({ task: repos.task }); debug('stockData created.'); telemetry = { purpose: { typeOf: TelemetryPurposeType.Stock }, object: { scope: TelemetryScope.Global, measuredAt: target.measuredAt }, result: stockData, startDate: startDate, endDate: new Date() }; } yield repos.telemetry.telemetryModel.create(telemetry); debug('telemetry saved.'); }); } /** * フロー計測データーを作成する * @param measuredFrom 計測開始日時 * @param measuredThrough 計測終了日時 */ // tslint:disable-next-line:no-single-line-block-comment /* istanbul ignore next */ function createSellerFlow(measuredFrom, measuredThrough, sellerId) { return (repos) => __awaiter(this, void 0, void 0, function* () { const { sellerFlowTransactionResult, expiredTransactionIds } = yield createSellerFlowTransactionResult(measuredFrom, measuredThrough, sellerId)(repos); // 期限切れ取引数 const numberOfTransactionsExpired = sellerFlowTransactionResult.numberOfExpired; // 期限切れ取引に対して作成されたアクションを取得 const actionsOnExpiredTransactions = yield repos.action.search({ typeOf: { $eq: factory.actionType.AuthorizeAction }, purpose: { id: { $in: expiredTransactionIds } } }, []); debug(actionsOnExpiredTransactions.length, 'actionsOnExpiredTransactions found.'); const numbersOfActionsOnExpired = expiredTransactionIds.map((transactionId) => { return actionsOnExpiredTransactions.filter((action) => { var _a; return ((_a = action.purpose) === null || _a === void 0 ? void 0 : _a.id) === transactionId; }).length; }); const totalNumberOfActionsOnExpired = numbersOfActionsOnExpired.reduce((a, b) => a + b, 0); const maxNumberOfActionsOnExpired = numbersOfActionsOnExpired.reduce((a, b) => Math.max(a, b), 0); const minNumberOfActionsOnExpired = numbersOfActionsOnExpired.reduce((a, b) => Math.min(a, b), (numberOfTransactionsExpired > 0) ? numbersOfActionsOnExpired[0] : 0); const averageNumberOfActionsOnExpired = (numberOfTransactionsExpired > 0) ? totalNumberOfActionsOnExpired / numberOfTransactionsExpired : 0; return { transactions: Object.assign(Object.assign({}, sellerFlowTransactionResult), { totalNumberOfActionsOnExpired: totalNumberOfActionsOnExpired, maxNumberOfActionsOnExpired: maxNumberOfActionsOnExpired, minNumberOfActionsOnExpired: minNumberOfActionsOnExpired, averageNumberOfActionsOnExpired: parseFloat(averageNumberOfActionsOnExpired.toFixed(1)), // tslint:disable-next-line:no-suspicious-comment totalNumberOfOrderItems: 0, // tslint:disable-next-line:no-suspicious-comment maxNumberOfOrderItems: 0, // tslint:disable-next-line:no-suspicious-comment minNumberOfOrderItems: 0, // tslint:disable-next-line:no-suspicious-comment averageNumberOfOrderItems: 0 // TODO 実装 }), measuredFrom: measuredFrom, measuredThrough: measuredThrough }; }); } function createSellerFlowTransactionResult(measuredFrom, measuredThrough, sellerId) { // tslint:disable-next-line:max-func-body-length return (repos) => __awaiter(this, void 0, void 0, function* () { // 計測期間内に開始された取引数を算出する const { count } = yield repos.transaction.count({ typeOf: factory.transactionType.PlaceOrder, seller: { ids: [sellerId] }, startFrom: measuredFrom, startThrough: measuredThrough }); const numberOfTransactionsStarted = count; // 計測期間内に開始され、かつ、すでに終了している取引を検索 const startedAndEndedTransactions = yield repos.transaction.projectFields({ typeOf: factory.transactionType.PlaceOrder, seller: { ids: [sellerId] }, startFrom: measuredFrom, startThrough: measuredThrough, endThrough: new Date(), inclusion: ['status'] }); const numberOfStartedAndConfirmed = startedAndEndedTransactions.filter((transaction) => transaction.status === factory.transactionStatusType.Confirmed).length; const numberOfStartedAndExpired = startedAndEndedTransactions.filter((transaction) => transaction.status === factory.transactionStatusType.Expired).length; const endedTransactions = yield repos.transaction.projectFields({ typeOf: factory.transactionType.PlaceOrder, seller: { ids: [sellerId] }, endFrom: measuredFrom, endThrough: measuredThrough, inclusion: ['status', 'endDate', 'startDate', 'result'] }); debug(endedTransactions.length, 'endedTransactions found.'); const confirmedTransactions = endedTransactions.filter((transaction) => transaction.status === factory.transactionStatusType.Confirmed); const expiredTransactions = endedTransactions.filter((transaction) => transaction.status === factory.transactionStatusType.Expired); const numberOfTransactionsConfirmed = confirmedTransactions.length; // 所要時間算出(期間の成立取引リストを取得し、開始時刻と成立時刻の差を所要時間とする) const requiredTimesConfirmed = confirmedTransactions.map((transaction) => moment(transaction.endDate) .diff(moment(transaction.startDate, 'milliseconds'))); const totalRequiredTimeInMilliseconds = requiredTimesConfirmed.reduce((a, b) => a + b, 0); const maxRequiredTimeInMilliseconds = requiredTimesConfirmed.reduce((a, b) => Math.max(a, b), 0); const minRequiredTimeInMilliseconds = requiredTimesConfirmed.reduce((a, b) => Math.min(a, b), (numberOfTransactionsConfirmed > 0) ? requiredTimesConfirmed[0] : 0); const averageRequiredTimeInMilliseconds = (numberOfTransactionsConfirmed > 0) ? totalRequiredTimeInMilliseconds / numberOfTransactionsConfirmed : 0; // const totalTimeLeftUntilEventInMilliseconds = timesLeftUntilEvent.reduce((a, b) => a + b, 0); // const maxTimeLeftUntilEventInMilliseconds = timesLeftUntilEvent.reduce((a, b) => Math.max(a, b), 0); // const minTimeLeftUntilEventInMilliseconds = // timesLeftUntilEvent.reduce((a, b) => Math.min(a, b), (numberOfTransactionsConfirmed > 0) ? timesLeftUntilEvent[0] : 0); // const averageTimeLeftUntilEventInMilliseconds = // (numberOfTransactionsConfirmed > 0) ? totalTimeLeftUntilEventInMilliseconds / numberOfTransactionsConfirmed : 0; // 金額算出 const amounts = confirmedTransactions.map((transaction) => transaction.result.order.price); const totalAmount = amounts.reduce((a, b) => a + b, 0); const maxAmount = amounts.reduce((a, b) => Math.max(a, b), 0); const minAmount = amounts.reduce((a, b) => Math.min(a, b), (numberOfTransactionsConfirmed > 0) ? amounts[0] : 0); const averageAmount = (numberOfTransactionsConfirmed > 0) ? totalAmount / numberOfTransactionsConfirmed : 0; // アクション数集計 // const numbersOfActions = confirmedTransactions.map((t) => t.object.authorizeActions.length); // const totalNumberOfActions = numbersOfActions.reduce((a, b) => a + b, 0); // const maxNumberOfActions = numbersOfActions.reduce((a, b) => Math.max(a, b), 0); // const minNumberOfActions = numbersOfActions.reduce( // (a, b) => Math.min(a, b), (numberOfTransactionsConfirmed > 0) ? numbersOfActions[0] : 0 // ); // const averageNumberOfActions = (numberOfTransactionsConfirmed > 0) ? totalNumberOfActions / numberOfTransactionsConfirmed : 0; // 期限切れ取引数 const numberOfTransactionsExpired = expiredTransactions.length; const expiredTransactionIds = expiredTransactions.map((transaction) => transaction.id); return { sellerFlowTransactionResult: { numberOfStarted: numberOfTransactionsStarted, numberOfStartedAndConfirmed: numberOfStartedAndConfirmed, numberOfStartedAndExpired: numberOfStartedAndExpired, numberOfConfirmed: numberOfTransactionsConfirmed, numberOfExpired: numberOfTransactionsExpired, totalRequiredTimeInMilliseconds: totalRequiredTimeInMilliseconds, maxRequiredTimeInMilliseconds: maxRequiredTimeInMilliseconds, minRequiredTimeInMilliseconds: minRequiredTimeInMilliseconds, averageRequiredTimeInMilliseconds: parseFloat(averageRequiredTimeInMilliseconds.toFixed(1)), // totalTimeLeftUntilEventInMilliseconds: totalTimeLeftUntilEventInMilliseconds, // maxTimeLeftUntilEventInMilliseconds: maxTimeLeftUntilEventInMilliseconds, // minTimeLeftUntilEventInMilliseconds: minTimeLeftUntilEventInMilliseconds, // averageTimeLeftUntilEventInMilliseconds: averageTimeLeftUntilEventInMilliseconds, totalAmount: totalAmount, maxAmount: maxAmount, minAmount: minAmount, averageAmount: parseFloat(averageAmount.toFixed(1)), // totalNumberOfActionsOnConfirmed: totalNumberOfActions, // maxNumberOfActionsOnConfirmed: maxNumberOfActions, // minNumberOfActionsOnConfirmed: minNumberOfActions, // averageNumberOfActionsOnConfirmed: parseFloat(averageNumberOfActions.toFixed(1)), // tslint:disable-next-line:no-suspicious-comment totalNumberOfOrderItems: 0, // TODO 実装 // tslint:disable-next-line:no-suspicious-comment maxNumberOfOrderItems: 0, // TODO 実装 // tslint:disable-next-line:no-suspicious-comment minNumberOfOrderItems: 0, // TODO 実装 // tslint:disable-next-line:no-suspicious-comment averageNumberOfOrderItems: 0 // TODO 実装 }, expiredTransactionIds }; }); } /** * ストック計測データを作成する * @param measuredAt 計測日時 */ // tslint:disable-next-line:no-single-line-block-comment /* istanbul ignore next */ function createSellerStock(measuredAt, sellerId) { return (repos) => __awaiter(this, void 0, void 0, function* () { // {measuredAt}以前に開始し、{measuredAt}以後に成立あるいは期限切れした取引 const { count } = yield repos.transaction.count({ typeOf: factory.transactionType.PlaceOrder, seller: { ids: [sellerId] }, startThrough: measuredAt, endFrom: measuredAt }); const numTransactionsUnderway1 = count; // {measuredAt}以前に開始し、いまだに進行中の取引 const count2Result = yield repos.transaction.count({ typeOf: factory.transactionType.PlaceOrder, seller: { ids: [sellerId] }, startThrough: measuredAt, statuses: [factory.transactionStatusType.InProgress] }); const numTransactionsUnderway2 = count2Result.count; const numberOfTransactionsUnderway = numTransactionsUnderway1 + numTransactionsUnderway2; debug('numberOfTransactionsUnderway:', numberOfTransactionsUnderway); return { transactions: { numberOfUnderway: numberOfTransactionsUnderway }, measuredAt: measuredAt }; }); } /** * フロー計測データーを作成する * @param measuredFrom 計測開始日時 * @param measuredThrough 計測終了日時 */ // tslint:disable-next-line:no-single-line-block-comment /* istanbul ignore next */ function createGlobalFlow(measuredFrom, measuredThrough) { return (repos) => __awaiter(this, void 0, void 0, function* () { // 全タスク名リスト const targetTaskNames = Object.keys(factory.taskName) .map((k) => factory.taskName[k]); const taskResults = yield Promise.all(targetTaskNames.map((taskName) => __awaiter(this, void 0, void 0, function* () { const numberOfTasksCreated = yield repos.task.taskModel.countDocuments({ name: taskName, createdAt: { $gte: measuredFrom, $lt: measuredThrough } }) .exec(); debug('numberOfTasksCreated:', numberOfTasksCreated); // 実行中止ステータスで、最終試行日時が範囲にあるものを実行タスク数とする const numberOfTasksAborted = yield repos.task.taskModel.countDocuments({ name: taskName, lastTriedAt: { $type: 'date', $gte: measuredFrom, $lt: measuredThrough }, status: factory.taskStatus.Aborted }) .exec(); debug('numberOfTasksAborted:', numberOfTasksAborted); // 実行済みステータスで、最終試行日時が範囲にあるものを実行タスク数とする const executedTasks = yield repos.task.taskModel.find({ name: taskName, lastTriedAt: { $type: 'date', $gte: measuredFrom, $lt: measuredThrough }, status: factory.taskStatus.Executed }, 'runsAt lastTriedAt numberOfTried') .exec() .then((docs) => docs.map((doc) => doc.toObject())); debug(executedTasks.length, 'executedTasks found.'); const numberOfTasksExecuted = executedTasks.length; const latencies = executedTasks.map((task) => moment(task.lastTriedAt) .diff(moment(task.runsAt, 'milliseconds'))); const totalLatency = latencies.reduce((a, b) => a + b, 0); const maxLatency = latencies.reduce((a, b) => Math.max(a, b), 0); const minLatency = latencies.reduce((a, b) => Math.min(a, b), (numberOfTasksExecuted > 0) ? latencies[0] : 0); const numbersOfTrials = yield Promise.all(executedTasks.map((task) => task.numberOfTried)); const totalNumberOfTrials = numbersOfTrials.reduce((a, b) => a + b, 0); const maxNumberOfTrials = numbersOfTrials.reduce((a, b) => Math.max(a, b), 0); const minNumberOfTrials = numbersOfTrials.reduce((a, b) => Math.min(a, b), (numberOfTasksExecuted > 0) ? numbersOfTrials[0] : 0); return { name: taskName, numberOfCreated: numberOfTasksCreated, numberOfExecuted: numberOfTasksExecuted, numberOfAborted: numberOfTasksAborted, totalLatencyInMilliseconds: totalLatency, maxLatencyInMilliseconds: maxLatency, minLatencyInMilliseconds: minLatency, totalNumberOfTrials: totalNumberOfTrials, maxNumberOfTrials: maxNumberOfTrials, minNumberOfTrials: minNumberOfTrials }; }))); return { tasks: taskResults, measuredFrom: measuredFrom, measuredThrough: measuredThrough }; }); } /** * ストック計測データを作成する * @param measuredAt 計測日時 */ // tslint:disable-next-line:no-single-line-block-comment /* istanbul ignore next */ function createGlobalStock(measuredAt) { return (repos) => __awaiter(this, void 0, void 0, function* () { // 待機状態のタスク数を算出 debug('counting waiting tasks globally...'); const numberOfTasksUnexecuted = yield repos.task.taskModel.countDocuments({ runsAt: { $lt: measuredAt }, // 実行日時を超過している status: { $in: [factory.taskStatus.Ready, factory.taskStatus.Running] } }) .exec(); debug('global waiting tasks count', numberOfTasksUnexecuted); return { tasks: { numberOfUnexecuted: numberOfTasksUnexecuted }, measuredAt: measuredAt }; }); }