diff --git a/packages/redux-devtools-rtk-query-monitor/README.md b/packages/redux-devtools-rtk-query-monitor/README.md index 18ae47e5..e2142e1f 100644 --- a/packages/redux-devtools-rtk-query-monitor/README.md +++ b/packages/redux-devtools-rtk-query-monitor/README.md @@ -72,7 +72,7 @@ See also - query state - tags - subscriptions - - api slice config + - api state - api stats ## TODO diff --git a/packages/redux-devtools-rtk-query-monitor/src/types.ts b/packages/redux-devtools-rtk-query-monitor/src/types.ts index 3697bb38..a8f270dc 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/types.ts +++ b/packages/redux-devtools-rtk-query-monitor/src/types.ts @@ -114,6 +114,7 @@ export interface QueryTimings { readonly latestFetch: { key: string; at: string } | null; readonly slowest: { key: string; duration: string } | null; readonly fastest: { key: string; duration: string } | null; + readonly average: string; } export interface ApiTimings { diff --git a/packages/redux-devtools-rtk-query-monitor/src/utils/formatters.ts b/packages/redux-devtools-rtk-query-monitor/src/utils/formatters.ts new file mode 100644 index 00000000..69a4e93b --- /dev/null +++ b/packages/redux-devtools-rtk-query-monitor/src/utils/formatters.ts @@ -0,0 +1,31 @@ +export function formatMs(milliseconds: number): string { + if (!Number.isFinite(milliseconds)) { + return 'NaN'; + } + + const absInput = Math.abs(Math.round(milliseconds)); + let millis = (absInput % 1000).toString(); + + if (millis.length < 3) { + if (millis.length === 2) { + millis = '0' + millis; + } else { + millis = '00' + millis; + } + } + + const seconds = Math.floor(absInput / 1_000) % 60; + const minutes = Math.floor(absInput / 60_000); + + let output = `${seconds}.${millis}s`; + + if (minutes > 0) { + output = `${minutes}m${output}`; + } + + if (milliseconds < 0) { + output = `-${output}`; + } + + return output; +} diff --git a/packages/redux-devtools-rtk-query-monitor/src/utils/rtk-query.ts b/packages/redux-devtools-rtk-query-monitor/src/utils/rtk-query.ts index 8fc56709..ae154432 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/utils/rtk-query.ts +++ b/packages/redux-devtools-rtk-query-monitor/src/utils/rtk-query.ts @@ -19,6 +19,8 @@ import { missingTagId } from '../monitor-config'; import { Comparator } from './comparators'; import { emptyArray } from './object'; import { SubscriptionState } from '@reduxjs/toolkit/dist/query/core/apiState'; +import { formatMs } from './formatters'; +import { mean } from './statistics'; const rtkqueryApiStateKeys: ReadonlyArray = [ 'queries', @@ -213,6 +215,8 @@ function computeQueryApiTimings( duration: Number.MAX_SAFE_INTEGER, }; + const pendingDurations: number[] = []; + const queryKeys = Object.keys(queriesOrMutations); for (let i = 0, len = queryKeys.length; i < len; i++) { @@ -239,6 +243,8 @@ function computeQueryApiTimings( ) { const pendingDuration = fulfilledTimeStamp - startedTimeStamp; + pendingDurations.push(pendingDuration); + if (pendingDuration > slowest.duration) { slowest.key = queryKey; slowest.duration = pendingDuration; @@ -265,22 +271,26 @@ function computeQueryApiTimings( } if (slowest.key !== null) { - slowest.duration = `${((slowest.duration as number) / 1_000).toFixed(3)}s`; + slowest.duration = formatMs(slowest.duration as number); } else { slowest = null; } if (fastest.key !== null) { - fastest.duration = `${((fastest.duration as number) / 1_000).toFixed(3)}s`; + fastest.duration = formatMs(fastest.duration as number); } else { fastest = null; } + const average = + pendingDurations.length > 0 ? formatMs(mean(pendingDurations)) : '-'; + return { latestFetch, oldestFetch, slowest, fastest, + average, } as QueryTimings; } diff --git a/packages/redux-devtools-rtk-query-monitor/src/utils/statistics.ts b/packages/redux-devtools-rtk-query-monitor/src/utils/statistics.ts new file mode 100644 index 00000000..daaff19c --- /dev/null +++ b/packages/redux-devtools-rtk-query-monitor/src/utils/statistics.ts @@ -0,0 +1,43 @@ +/** + * An implementation of `Kahan-Babuska algorithm` + * that reduces numerical floating point errors. + * @param {number[]} nums + * @returns {number} + * @see https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.582.288&rep=rep1&type=pdf + */ +function sum(nums: number[]): number { + if (nums.length === 0) { + return 0; + } + + let t; + let correction = 0; + let output = nums[0]; + + for (let i = 1, len = nums.length; i < len; i++) { + t = output + nums[i]; + + if (Math.abs(output) >= Math.abs(nums[i])) { + correction += output - t + nums[i]; + } else { + correction += nums[i] - t + output; + } + + output = t; + } + + return output + correction; +} + +/** + * Returns mean, also known as average, of numerical sequences. + * @param nums + * @returns + */ +export function mean(nums: number[]): number { + if (nums.length === 0) { + return NaN; + } + + return sum(nums) / nums.length; +}