diff --git a/packages/api/src/controllers/jsldata.js b/packages/api/src/controllers/jsldata.js index dbace5a25..697a561d3 100644 --- a/packages/api/src/controllers/jsldata.js +++ b/packages/api/src/controllers/jsldata.js @@ -102,11 +102,12 @@ module.exports = { async ensureDatastore(jslid, formatterFunction) { const rowFormatter = requirePluginFunction(formatterFunction); - let datastore = this.datastores[jslid]; + const dskey = `${jslid}||${formatterFunction}`; + let datastore = this.datastores[dskey]; if (!datastore) { datastore = new JsonLinesDatastore(getJslFileName(jslid), rowFormatter); // datastore = new DatastoreProxy(getJslFileName(jslid)); - this.datastores[jslid] = datastore; + this.datastores[dskey] = datastore; } return datastore; }, @@ -190,4 +191,48 @@ module.exports = { await fs.promises.writeFile(getJslFileName(jslid), text); return true; }, + + extractTimelineChart_meta: true, + async extractTimelineChart({ jslid, formatterFunction, measures }) { + const formater = requirePluginFunction(formatterFunction); + const datastore = new JsonLinesDatastore(getJslFileName(jslid), formater); + 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; + 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)); + + const datasets = measures.map(m => ({ + label: m.label, + data: Array(STEPS).fill(0), + })); + + // 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]; + } + return true; + }); + + datastore._closeReader(); + + return { + labels, + datasets, + }; + }, }; diff --git a/packages/types/engines.d.ts b/packages/types/engines.d.ts index 1976e9d3b..dae480ed6 100644 --- a/packages/types/engines.d.ts +++ b/packages/types/engines.d.ts @@ -79,6 +79,8 @@ export interface EngineDriver { supportsServerSummary?: boolean; supportsDatabaseProfiler?: boolean; profilerFormatterFunction?: string; + profilerChartFormatterFunction?: string; + profilerChartMeasures?: { label: string; field: string }[]; isElectronOnly?: boolean; supportedCreateDatabase?: boolean; showConnectionField?: (field: string, values: any) => boolean; diff --git a/packages/web/src/appobj/ArchiveFileAppObject.svelte b/packages/web/src/appobj/ArchiveFileAppObject.svelte index 6eb04de32..629782d52 100644 --- a/packages/web/src/appobj/ArchiveFileAppObject.svelte +++ b/packages/web/src/appobj/ArchiveFileAppObject.svelte @@ -214,7 +214,9 @@ tabComponent: 'ProfilerTab', props: { jslid: `archive://${data.folderName}/${data.fileName}`, - formatterFunction: eng.profilerFormatterFunction, + profilerFormatterFunction: eng.profilerFormatterFunction, + profilerChartFormatterFunction: eng.profilerChartFormatterFunction, + profilerChartMeasures: eng.profilerChartMeasures, }, }); }, diff --git a/packages/web/src/tabs/ProfilerTab.svelte b/packages/web/src/tabs/ProfilerTab.svelte index 5f2b7caf1..33ae37bc0 100644 --- a/packages/web/src/tabs/ProfilerTab.svelte +++ b/packages/web/src/tabs/ProfilerTab.svelte @@ -1,6 +1,4 @@ {#if jslid} - + + + + + + {#if isLoadingChart} + + {:else} + + {/if} + + {:else} {/if} - diff --git a/plugins/dbgate-plugin-mongo/src/backend/index.js b/plugins/dbgate-plugin-mongo/src/backend/index.js index 0f8fbd10c..7b6e0f5d9 100644 --- a/plugins/dbgate-plugin-mongo/src/backend/index.js +++ b/plugins/dbgate-plugin-mongo/src/backend/index.js @@ -1,10 +1,12 @@ const driver = require('./driver'); const formatProfilerEntry = require('../frontend/formatProfilerEntry'); +const formatProfilerChartEntry = require('../frontend/formatProfilerChartEntry'); module.exports = { packageName: 'dbgate-plugin-mongo', drivers: [driver], functions: { formatProfilerEntry, + formatProfilerChartEntry, }, }; diff --git a/plugins/dbgate-plugin-mongo/src/frontend/driver.js b/plugins/dbgate-plugin-mongo/src/frontend/driver.js index 53887eca6..ab8787c20 100644 --- a/plugins/dbgate-plugin-mongo/src/frontend/driver.js +++ b/plugins/dbgate-plugin-mongo/src/frontend/driver.js @@ -35,6 +35,11 @@ const driver = { supportsServerSummary: true, supportsDatabaseProfiler: true, profilerFormatterFunction: 'formatProfilerEntry@dbgate-plugin-mongo', + profilerChartFormatterFunction: 'formatProfilerChartEntry@dbgate-plugin-mongo', + profilerChartMeasures: [ + { label: 'Req count', field: 'count' }, + { label: 'Duration', field: 'millis' }, + ], databaseUrlPlaceholder: 'e.g. mongodb://username:password@mongodb.mydomain.net/dbname', getQuerySplitterOptions: () => mongoSplitterOptions, diff --git a/plugins/dbgate-plugin-mongo/src/frontend/formatProfilerChartEntry.js b/plugins/dbgate-plugin-mongo/src/frontend/formatProfilerChartEntry.js new file mode 100644 index 000000000..8ee5a27a4 --- /dev/null +++ b/plugins/dbgate-plugin-mongo/src/frontend/formatProfilerChartEntry.js @@ -0,0 +1,14 @@ +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/formatProfilerEntry.js b/plugins/dbgate-plugin-mongo/src/frontend/formatProfilerEntry.js index cb57e8b3a..9b4e4cedb 100644 --- a/plugins/dbgate-plugin-mongo/src/frontend/formatProfilerEntry.js +++ b/plugins/dbgate-plugin-mongo/src/frontend/formatProfilerEntry.js @@ -2,6 +2,7 @@ const _ = require('lodash'); function formatProfilerEntry(obj) { const ts = obj.ts; + const stats = { millis: obj.millis }; let op = obj.op; let doc; let query; @@ -64,6 +65,7 @@ function formatProfilerEntry(obj) { doc, query, ext, + stats, }; } diff --git a/plugins/dbgate-plugin-mongo/src/frontend/index.js b/plugins/dbgate-plugin-mongo/src/frontend/index.js index fbe5cc8d8..c68b8daa5 100644 --- a/plugins/dbgate-plugin-mongo/src/frontend/index.js +++ b/plugins/dbgate-plugin-mongo/src/frontend/index.js @@ -1,10 +1,12 @@ import driver from './driver'; import formatProfilerEntry from './formatProfilerEntry'; +import formatProfilerChartEntry from './formatProfilerChartEntry'; export default { packageName: 'dbgate-plugin-mongo', drivers: [driver], functions: { formatProfilerEntry, + formatProfilerChartEntry, }, };