diff --git a/packages/api/src/controllers/jsldata.js b/packages/api/src/controllers/jsldata.js index 697a561d..8b0d29ff 100644 --- a/packages/api/src/controllers/jsldata.js +++ b/packages/api/src/controllers/jsldata.js @@ -193,46 +193,83 @@ module.exports = { }, extractTimelineChart_meta: true, - async extractTimelineChart({ jslid, formatterFunction, measures }) { - const formater = requirePluginFunction(formatterFunction); - const datastore = new JsonLinesDatastore(getJslFileName(jslid), formater); + async extractTimelineChart({ jslid, timestampFunction, aggregateFunction, measures }) { + const timestamp = requirePluginFunction(timestampFunction); + const aggregate = requirePluginFunction(aggregateFunction); + const datastore = new JsonLinesDatastore(getJslFileName(jslid)); let mints = null; let maxts = null; // pass 1 - counts stats, time range await datastore.enumRows(row => { - if (!mints || row.ts < mints) mints = row.ts; - if (!maxts || row.ts > maxts) maxts = row.ts; + const ts = timestamp(row); + if (!mints || ts < mints) mints = ts; + if (!maxts || ts > maxts) maxts = ts; return true; }); const minTime = new Date(mints).getTime(); const maxTime = new Date(maxts).getTime(); const duration = maxTime - minTime; const STEPS = 100; - const step = duration / STEPS; - const labels = _.range(STEPS).map(i => new Date(minTime + step / 2 + step * i)); + let stepCount = duration > 100 * 1000 ? STEPS : Math.round((maxTime - minTime) / 1000); + if (stepCount < 2) { + stepCount = 2; + } + const stepDuration = duration / stepCount; + const labels = _.range(stepCount).map(i => new Date(minTime + stepDuration / 2 + stepDuration * i)); - const datasets = measures.map(m => ({ - label: m.label, - data: Array(STEPS).fill(0), + // const datasets = measures.map(m => ({ + // label: m.label, + // data: Array(stepCount).fill(0), + // })); + + const mproc = measures.map(m => ({ + ...m, })); + const data = Array(stepCount) + .fill(0) + .map(() => ({})); + // pass 2 - count measures await datastore.enumRows(row => { - if (!mints || row.ts < mints) mints = row.ts; - if (!maxts || row.ts > maxts) maxts = row.ts; - - for (let i = 0; i < measures.length; i++) { - const part = Math.round((new Date(row.ts).getTime() - minTime) / step); - datasets[i].data[part] += row[measures[i].field]; + const ts = timestamp(row); + let part = Math.round((new Date(ts).getTime() - minTime) / stepDuration); + if (part < 0) part = 0; + if (part >= stepCount) part - stepCount - 1; + if (data[part]) { + data[part] = aggregate(data[part], row, stepDuration); } return true; }); datastore._closeReader(); + // const measureByField = _.fromPairs(measures.map((m, i) => [m.field, i])); + + // for (let mindex = 0; mindex < measures.length; mindex++) { + // for (let stepIndex = 0; stepIndex < stepCount; stepIndex++) { + // const measure = measures[mindex]; + // if (measure.perSecond) { + // datasets[mindex].data[stepIndex] /= stepDuration / 1000; + // } + // if (measure.perField) { + // datasets[mindex].data[stepIndex] /= datasets[measureByField[measure.perField]].data[stepIndex]; + // } + // } + // } + + // for (let i = 0; i < measures.length; i++) { + // if (measures[i].hidden) { + // datasets[i] = null; + // } + // } + return { labels, - datasets, + datasets: mproc.map(m => ({ + label: m.label, + data: data.map(d => d[m.field] || 0), + })), }; }, }; diff --git a/packages/types/engines.d.ts b/packages/types/engines.d.ts index dae480ed..fac146b7 100644 --- a/packages/types/engines.d.ts +++ b/packages/types/engines.d.ts @@ -79,7 +79,8 @@ export interface EngineDriver { supportsServerSummary?: boolean; supportsDatabaseProfiler?: boolean; profilerFormatterFunction?: string; - profilerChartFormatterFunction?: string; + profilerTimestampFunction?: string; + profilerChartAggregateFunction?: string; profilerChartMeasures?: { label: string; field: string }[]; isElectronOnly?: boolean; supportedCreateDatabase?: boolean; diff --git a/packages/web/src/appobj/ArchiveFileAppObject.svelte b/packages/web/src/appobj/ArchiveFileAppObject.svelte index 629782d5..0fcad808 100644 --- a/packages/web/src/appobj/ArchiveFileAppObject.svelte +++ b/packages/web/src/appobj/ArchiveFileAppObject.svelte @@ -215,7 +215,8 @@ props: { jslid: `archive://${data.folderName}/${data.fileName}`, profilerFormatterFunction: eng.profilerFormatterFunction, - profilerChartFormatterFunction: eng.profilerChartFormatterFunction, + profilerTimestampFunction: eng.profilerTimestampFunction, + profilerChartAggregateFunction: eng.profilerChartAggregateFunction, profilerChartMeasures: eng.profilerChartMeasures, }, }); diff --git a/packages/web/src/tabs/ProfilerTab.svelte b/packages/web/src/tabs/ProfilerTab.svelte index 33ae37bc..8bd0cb95 100644 --- a/packages/web/src/tabs/ProfilerTab.svelte +++ b/packages/web/src/tabs/ProfilerTab.svelte @@ -58,7 +58,8 @@ export let database; export let jslid; export let profilerFormatterFunction; - export let profilerChartFormatterFunction; + export let profilerTimestampFunction; + export let profilerChartAggregateFunction; export let profilerChartMeasures; let profiling = false; @@ -128,7 +129,8 @@ const data = await apiCall('jsldata/extract-timeline-chart', { jslid, - formatterFunction: profilerChartFormatterFunction || engine.profilerChartFormatterFunction, + timestampFunction: profilerTimestampFunction || engine.profilerTimestampFunction, + aggregateFunction: profilerChartAggregateFunction || engine.profilerChartAggregateFunction, measures: profilerChartMeasures || engine.profilerChartMeasures, }); chartData = { diff --git a/plugins/dbgate-plugin-mongo/src/backend/index.js b/plugins/dbgate-plugin-mongo/src/backend/index.js index 7b6e0f5d..2743dd1b 100644 --- a/plugins/dbgate-plugin-mongo/src/backend/index.js +++ b/plugins/dbgate-plugin-mongo/src/backend/index.js @@ -1,12 +1,16 @@ const driver = require('./driver'); -const formatProfilerEntry = require('../frontend/formatProfilerEntry'); -const formatProfilerChartEntry = require('../frontend/formatProfilerChartEntry'); +const { + formatProfilerEntry, + extractProfileTimestamp, + aggregateProfileChartEntry, +} = require('../frontend/profilerFunctions'); module.exports = { packageName: 'dbgate-plugin-mongo', drivers: [driver], functions: { formatProfilerEntry, - formatProfilerChartEntry, + extractProfileTimestamp, + aggregateProfileChartEntry, }, }; diff --git a/plugins/dbgate-plugin-mongo/src/frontend/driver.js b/plugins/dbgate-plugin-mongo/src/frontend/driver.js index ab8787c2..39f5de88 100644 --- a/plugins/dbgate-plugin-mongo/src/frontend/driver.js +++ b/plugins/dbgate-plugin-mongo/src/frontend/driver.js @@ -35,10 +35,15 @@ const driver = { supportsServerSummary: true, supportsDatabaseProfiler: true, profilerFormatterFunction: 'formatProfilerEntry@dbgate-plugin-mongo', - profilerChartFormatterFunction: 'formatProfilerChartEntry@dbgate-plugin-mongo', + profilerTimestampFunction: 'extractProfileTimestamp@dbgate-plugin-mongo', + profilerChartAggregateFunction: 'aggregateProfileChartEntry@dbgate-plugin-mongo', profilerChartMeasures: [ - { label: 'Req count', field: 'count' }, - { label: 'Duration', field: 'millis' }, + { label: 'Req count/s', field: 'countPerSec' }, + { label: 'Avg duration', field: 'avgDuration' }, + + // { label: 'Req count/s', field: 'countPerSec', perSecond: true }, + // { field: 'countAll', hidden: true }, + // { label: 'Avg duration', field: 'millis', perField: 'countAll' }, ], databaseUrlPlaceholder: 'e.g. mongodb://username:password@mongodb.mydomain.net/dbname', diff --git a/plugins/dbgate-plugin-mongo/src/frontend/formatProfilerChartEntry.js b/plugins/dbgate-plugin-mongo/src/frontend/formatProfilerChartEntry.js deleted file mode 100644 index 8ee5a27a..00000000 --- a/plugins/dbgate-plugin-mongo/src/frontend/formatProfilerChartEntry.js +++ /dev/null @@ -1,14 +0,0 @@ -const _ = require('lodash'); -const formatProfilerEntry = require('./formatProfilerEntry'); - -function formatProfilerChartEntry(obj) { - const fmt = formatProfilerEntry(obj); - - return { - ts: fmt.ts, - millis: fmt.stats.millis, - count: 1, - }; -} - -module.exports = formatProfilerChartEntry; diff --git a/plugins/dbgate-plugin-mongo/src/frontend/index.js b/plugins/dbgate-plugin-mongo/src/frontend/index.js index c68b8daa..da0a7758 100644 --- a/plugins/dbgate-plugin-mongo/src/frontend/index.js +++ b/plugins/dbgate-plugin-mongo/src/frontend/index.js @@ -1,12 +1,12 @@ import driver from './driver'; -import formatProfilerEntry from './formatProfilerEntry'; -import formatProfilerChartEntry from './formatProfilerChartEntry'; +import { formatProfilerEntry, extractProfileTimestamp, aggregateProfileChartEntry } from './profilerFunctions'; export default { packageName: 'dbgate-plugin-mongo', drivers: [driver], functions: { formatProfilerEntry, - formatProfilerChartEntry, + extractProfileTimestamp, + aggregateProfileChartEntry, }, }; diff --git a/plugins/dbgate-plugin-mongo/src/frontend/formatProfilerEntry.js b/plugins/dbgate-plugin-mongo/src/frontend/profilerFunctions.js similarity index 74% rename from plugins/dbgate-plugin-mongo/src/frontend/formatProfilerEntry.js rename to plugins/dbgate-plugin-mongo/src/frontend/profilerFunctions.js index 9b4e4ced..fdff1fbe 100644 --- a/plugins/dbgate-plugin-mongo/src/frontend/formatProfilerEntry.js +++ b/plugins/dbgate-plugin-mongo/src/frontend/profilerFunctions.js @@ -69,4 +69,33 @@ function formatProfilerEntry(obj) { }; } -module.exports = formatProfilerEntry; +function extractProfileTimestamp(obj) { + return obj.ts; +} + +function aggregateProfileChartEntry(aggr, obj, stepDuration) { + // const fmt = formatProfilerEntry(obj); + + const countAll = (aggr.countAll || 0) + 1; + const sumMillis = (aggr.sumMillis || 0) + obj.millis; + + return { + countAll, + sumMillis, + countPerSec: (countAll / stepDuration) * 1000, + avgDuration: sumMillis / countAll, + }; + + // return { + // ts: fmt.ts, + // millis: fmt.stats.millis, + // countAll: 1, + // countPerSec: 1, + // }; +} + +module.exports = { + formatProfilerEntry, + extractProfileTimestamp, + aggregateProfileChartEntry, +};