From ba9b0cf2c92a221a7565e3524515490bf3149799 Mon Sep 17 00:00:00 2001 From: Mikhail Rodichenko Date: Thu, 20 Apr 2023 17:41:47 +0200 Subject: [PATCH] Improve run billing estimation (#3201) - GUI, consider instance start date for run price estimation, backported to stage/0.16/uat --- client/src/components/runs/logs/Log.js | 3 ++ .../search/preview/PipelineRunPreview.js | 3 ++ client/src/utils/evaluate-run-price.js | 8 ++-- client/src/utils/run-duration/index.js | 48 ++++++++++++++++++- .../run-duration/run-statuses-timeline.js | 1 + 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/client/src/components/runs/logs/Log.js b/client/src/components/runs/logs/Log.js index 33a117acd4..49a6a7aa39 100644 --- a/client/src/components/runs/logs/Log.js +++ b/client/src/components/runs/logs/Log.js @@ -1805,6 +1805,9 @@ class Logs extends localization.LocalizedReactComponent { let price; if (pricePerHour) { const adjustPrice = (value) => { + if (value === 0) { + return 0; + } let cents = Math.ceil(value * 100); if (cents < 1) { cents = 1; diff --git a/client/src/components/search/preview/PipelineRunPreview.js b/client/src/components/search/preview/PipelineRunPreview.js index 6453b85303..0ddf3fcd85 100644 --- a/client/src/components/search/preview/PipelineRunPreview.js +++ b/client/src/components/search/preview/PipelineRunPreview.js @@ -331,6 +331,9 @@ export default class PipelineRunPreview extends React.Component { lineHeight: '20px' }; const adjustPrice = (value) => { + if (value === 0) { + return 0; + } let cents = Math.ceil(value * 100); if (cents < 1) { cents = 1; diff --git a/client/src/utils/evaluate-run-price.js b/client/src/utils/evaluate-run-price.js index 123e27810b..f98b445486 100644 --- a/client/src/utils/evaluate-run-price.js +++ b/client/src/utils/evaluate-run-price.js @@ -46,8 +46,8 @@ export default function evaluateRunPrice ( }; } const { - totalDuration, - totalNonPausedDuration + totalBillableDuration, + totalBillableRunningDuration } = getRunDurationInfo( run, analyseSchedulingPhase, @@ -59,8 +59,8 @@ export default function evaluateRunPrice ( workersPrice = 0 } = run; const format = (value) => Math.ceil(value * 100.0) / 100.0; - const master = computePricePerHour * (totalNonPausedDuration / SECONDS_IN_HOUR) + - diskPricePerHour * (totalDuration / SECONDS_IN_HOUR); + const master = computePricePerHour * (totalBillableRunningDuration / SECONDS_IN_HOUR) + + diskPricePerHour * (totalBillableDuration / SECONDS_IN_HOUR); return { master: format(master), workers: format(workersPrice), diff --git a/client/src/utils/run-duration/index.js b/client/src/utils/run-duration/index.js index 6d158e3a37..9b0b9bccea 100644 --- a/client/src/utils/run-duration/index.js +++ b/client/src/utils/run-duration/index.js @@ -40,6 +40,8 @@ export {RunHistoryPhase}; * @property {boolean} wasPaused - if run was ever paused * @property {number} totalDuration - total duration (from job submission) in seconds * @property {number} totalRunningDuration - total running duration in seconds + * @property {number} totalBillableDuration - total billable duration in seconds + * @property {number} totalBillableRunningDuration - total billable and running duration in seconds * (duration after initialization, including paused intervals) * @property {number} totalNonPausedDuration - total non-paused duration in seconds * (scheduling stages, running stages) @@ -119,6 +121,35 @@ function getIntervalsTotalDuration (intervals = []) { .reduce((duration, interval) => duration + getIntervalDuration(interval), 0); } +/** + * @param {string} fromDate + * @param {RunInterval[]} intervals + * @param {RunHistoryPhase} phase + * @returns {number} + */ +function getRunningDuration (fromDate, intervals, ...phase) { + if (!fromDate) { + return 0; + } + const date = moment.utc(fromDate); + const filtered = (intervals || []) + .filter((interval) => phase.includes(interval.phase)) + .filter((interval) => interval.start >= date || (!interval.end || interval.end > date)) + .map((interval) => { + const { + start + } = interval; + if (start >= date) { + return interval; + } + return { + ...interval, + start: date + }; + }); + return getIntervalsTotalDuration(filtered); +} + /** * Gets run duration info (running, paused, paused intervals etc.) * @param {RunInfo} run @@ -163,6 +194,19 @@ export default function getRunDurationInfo ( .filter((interval) => interval.phase === RunHistoryPhase.paused); const scheduledIntervals = filteredIntervals .filter((interval) => interval.phase === RunHistoryPhase.scheduled); + const totalBillableDuration = getRunningDuration( + run.instanceStartDate, + filteredIntervals, + RunHistoryPhase.running, + RunHistoryPhase.paused, + RunHistoryPhase.scheduled + ); + const totalBillableRunningDuration = getRunningDuration( + run.instanceStartDate, + filteredIntervals, + RunHistoryPhase.running, + RunHistoryPhase.scheduled + ); const activeDuration = getIntervalsTotalDuration(runningIntervals); const pausedDuration = getIntervalsTotalDuration(pausedIntervals); const schedulingDuration = getIntervalsTotalDuration(scheduledIntervals); @@ -184,6 +228,8 @@ export default function getRunDurationInfo ( runningIntervals, scheduledIntervals, runningDate, - scheduledDate + scheduledDate, + totalBillableDuration, + totalBillableRunningDuration }; } diff --git a/client/src/utils/run-duration/run-statuses-timeline.js b/client/src/utils/run-duration/run-statuses-timeline.js index c4f4872733..c362c9d9b2 100644 --- a/client/src/utils/run-duration/run-statuses-timeline.js +++ b/client/src/utils/run-duration/run-statuses-timeline.js @@ -32,6 +32,7 @@ import moment from 'moment-timezone'; * @typedef {object} RunInfo * @property {string} [startDate] * @property {string} [endDate] + * @property {string} [instanceStartDate] * @property {string} [status] * @property {RunStatusInfo[]} [runStatuses] */