mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-05-03 01:23:57 +00:00
profiler charts
This commit is contained in:
@@ -102,11 +102,12 @@ module.exports = {
|
|||||||
|
|
||||||
async ensureDatastore(jslid, formatterFunction) {
|
async ensureDatastore(jslid, formatterFunction) {
|
||||||
const rowFormatter = requirePluginFunction(formatterFunction);
|
const rowFormatter = requirePluginFunction(formatterFunction);
|
||||||
let datastore = this.datastores[jslid];
|
const dskey = `${jslid}||${formatterFunction}`;
|
||||||
|
let datastore = this.datastores[dskey];
|
||||||
if (!datastore) {
|
if (!datastore) {
|
||||||
datastore = new JsonLinesDatastore(getJslFileName(jslid), rowFormatter);
|
datastore = new JsonLinesDatastore(getJslFileName(jslid), rowFormatter);
|
||||||
// datastore = new DatastoreProxy(getJslFileName(jslid));
|
// datastore = new DatastoreProxy(getJslFileName(jslid));
|
||||||
this.datastores[jslid] = datastore;
|
this.datastores[dskey] = datastore;
|
||||||
}
|
}
|
||||||
return datastore;
|
return datastore;
|
||||||
},
|
},
|
||||||
@@ -190,4 +191,48 @@ module.exports = {
|
|||||||
await fs.promises.writeFile(getJslFileName(jslid), text);
|
await fs.promises.writeFile(getJslFileName(jslid), text);
|
||||||
return true;
|
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,
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
2
packages/types/engines.d.ts
vendored
2
packages/types/engines.d.ts
vendored
@@ -79,6 +79,8 @@ export interface EngineDriver {
|
|||||||
supportsServerSummary?: boolean;
|
supportsServerSummary?: boolean;
|
||||||
supportsDatabaseProfiler?: boolean;
|
supportsDatabaseProfiler?: boolean;
|
||||||
profilerFormatterFunction?: string;
|
profilerFormatterFunction?: string;
|
||||||
|
profilerChartFormatterFunction?: string;
|
||||||
|
profilerChartMeasures?: { label: string; field: string }[];
|
||||||
isElectronOnly?: boolean;
|
isElectronOnly?: boolean;
|
||||||
supportedCreateDatabase?: boolean;
|
supportedCreateDatabase?: boolean;
|
||||||
showConnectionField?: (field: string, values: any) => boolean;
|
showConnectionField?: (field: string, values: any) => boolean;
|
||||||
|
|||||||
@@ -214,7 +214,9 @@
|
|||||||
tabComponent: 'ProfilerTab',
|
tabComponent: 'ProfilerTab',
|
||||||
props: {
|
props: {
|
||||||
jslid: `archive://${data.folderName}/${data.fileName}`,
|
jslid: `archive://${data.folderName}/${data.fileName}`,
|
||||||
formatterFunction: eng.profilerFormatterFunction,
|
profilerFormatterFunction: eng.profilerFormatterFunction,
|
||||||
|
profilerChartFormatterFunction: eng.profilerChartFormatterFunction,
|
||||||
|
profilerChartMeasures: eng.profilerChartMeasures,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
export const matchingProps = ['conid', 'database', 'pureName', 'sql'];
|
|
||||||
|
|
||||||
const getCurrentEditor = () => getActiveComponent('ProfilerTab');
|
const getCurrentEditor = () => getActiveComponent('ProfilerTab');
|
||||||
|
|
||||||
registerCommand({
|
registerCommand({
|
||||||
@@ -50,18 +48,25 @@
|
|||||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||||
import { useConnectionInfo } from '../utility/metadataLoaders';
|
import { useConnectionInfo } from '../utility/metadataLoaders';
|
||||||
import { extensions } from '../stores';
|
import { extensions } from '../stores';
|
||||||
|
import ChartCore from '../charts/ChartCore.svelte';
|
||||||
|
import LoadingInfo from '../elements/LoadingInfo.svelte';
|
||||||
|
import randomcolor from 'randomcolor';
|
||||||
|
|
||||||
export const activator = createActivator('ProfilerTab', true);
|
export const activator = createActivator('ProfilerTab', true);
|
||||||
|
|
||||||
export let conid;
|
export let conid;
|
||||||
export let database;
|
export let database;
|
||||||
export let jslid;
|
export let jslid;
|
||||||
export let formatterFunction;
|
export let profilerFormatterFunction;
|
||||||
|
export let profilerChartFormatterFunction;
|
||||||
|
export let profilerChartMeasures;
|
||||||
|
|
||||||
let profiling = false;
|
let profiling = false;
|
||||||
let sessionId;
|
let sessionId;
|
||||||
|
let isLoadingChart = false;
|
||||||
|
|
||||||
let intervalId;
|
let intervalId;
|
||||||
|
let chartData;
|
||||||
|
|
||||||
$: connection = useConnectionInfo({ conid });
|
$: connection = useConnectionInfo({ conid });
|
||||||
$: engine = findEngineDriver($connection, $extensions);
|
$: engine = findEngineDriver($connection, $extensions);
|
||||||
@@ -74,6 +79,10 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 15 * 1000);
|
}, 15 * 1000);
|
||||||
|
|
||||||
|
if (jslid) {
|
||||||
|
loadChart();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@@ -109,11 +118,37 @@
|
|||||||
return conid && database && !isProfiling;
|
return conid && database && !isProfiling;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stopProfiling() {
|
async function loadChart() {
|
||||||
|
isLoadingChart = true;
|
||||||
|
|
||||||
|
const colors = randomcolor({
|
||||||
|
count: (profilerChartMeasures || engine.profilerChartMeasures).length,
|
||||||
|
seed: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await apiCall('jsldata/extract-timeline-chart', {
|
||||||
|
jslid,
|
||||||
|
formatterFunction: profilerChartFormatterFunction || engine.profilerChartFormatterFunction,
|
||||||
|
measures: profilerChartMeasures || engine.profilerChartMeasures,
|
||||||
|
});
|
||||||
|
chartData = {
|
||||||
|
...data,
|
||||||
|
labels: data.labels.map(x => new Date(x)),
|
||||||
|
datasets: data.datasets.map((x, i) => ({
|
||||||
|
...x,
|
||||||
|
borderColor: colors[i],
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
isLoadingChart = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function stopProfiling() {
|
||||||
profiling = false;
|
profiling = false;
|
||||||
apiCall('sessions/stop-profiler', { sesid: sessionId });
|
apiCall('sessions/stop-profiler', { sesid: sessionId });
|
||||||
|
|
||||||
invalidateCommands();
|
invalidateCommands();
|
||||||
|
|
||||||
|
loadChart();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stopProfilingEnabled() {
|
export function stopProfilingEnabled() {
|
||||||
@@ -137,27 +172,76 @@
|
|||||||
onSave: doSave,
|
onSave: doSave,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const data = [
|
||||||
|
// { year: 2010, count: 10 },
|
||||||
|
// { year: 2011, count: 20 },
|
||||||
|
// { year: 2012, count: 15 },
|
||||||
|
// { year: 2013, count: 25 },
|
||||||
|
// { year: 2014, count: 22 },
|
||||||
|
// { year: 2015, count: 30 },
|
||||||
|
// { year: 2016, count: 28 },
|
||||||
|
// ];
|
||||||
|
// {
|
||||||
|
// labels: data.map(row => row.year),
|
||||||
|
// datasets: [
|
||||||
|
// {
|
||||||
|
// label: 'Acquisitions by year',
|
||||||
|
// data: data.map(row => row.count),
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ToolStripContainer>
|
<ToolStripContainer>
|
||||||
{#if jslid}
|
{#if jslid}
|
||||||
|
<VerticalSplitter allowCollapseChild1 allowCollapseChild2>
|
||||||
|
<svelte:fragment slot="1">
|
||||||
<JslDataGrid
|
<JslDataGrid
|
||||||
{jslid}
|
{jslid}
|
||||||
listenInitializeFile
|
listenInitializeFile
|
||||||
formatterFunction={formatterFunction || engine?.profilerFormatterFunction}
|
formatterFunction={profilerFormatterFunction || engine?.profilerFormatterFunction}
|
||||||
/>
|
/>
|
||||||
|
</svelte:fragment>
|
||||||
|
<svelte:fragment slot="2">
|
||||||
|
{#if isLoadingChart}
|
||||||
|
<LoadingInfo wrapper message="Loading chart" />
|
||||||
|
{:else}
|
||||||
|
<ChartCore
|
||||||
|
title="Profile data"
|
||||||
|
data={chartData}
|
||||||
|
options={{
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
type: 'time',
|
||||||
|
distribution: 'linear',
|
||||||
|
|
||||||
|
time: {
|
||||||
|
tooltipFormat: 'D. M. YYYY HH:mm',
|
||||||
|
displayFormats: {
|
||||||
|
millisecond: 'HH:mm:ss.SSS',
|
||||||
|
second: 'HH:mm:ss',
|
||||||
|
minute: 'HH:mm',
|
||||||
|
hour: 'D.M hA',
|
||||||
|
day: 'D. M.',
|
||||||
|
week: 'D. M. YYYY',
|
||||||
|
month: 'MM-YYYY',
|
||||||
|
quarter: '[Q]Q - YYYY',
|
||||||
|
year: 'YYYY',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</svelte:fragment>
|
||||||
|
</VerticalSplitter>
|
||||||
{:else}
|
{:else}
|
||||||
<ErrorInfo message="Profiler not yet started" alignTop />
|
<ErrorInfo message="Profiler not yet started" alignTop />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- <VerticalSplitter>
|
|
||||||
<svelte:fragment slot="1">
|
|
||||||
{#if jslid}
|
|
||||||
<JslDataGrid {jslid} listenInitializeFile />
|
|
||||||
{/if}
|
|
||||||
</svelte:fragment>
|
|
||||||
<svelte:fragment slot="2">DETAIL</svelte:fragment>
|
|
||||||
</VerticalSplitter> -->
|
|
||||||
<svelte:fragment slot="toolstrip">
|
<svelte:fragment slot="toolstrip">
|
||||||
<ToolStripCommandButton command="profiler.start" />
|
<ToolStripCommandButton command="profiler.start" />
|
||||||
<ToolStripCommandButton command="profiler.stop" />
|
<ToolStripCommandButton command="profiler.stop" />
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
const driver = require('./driver');
|
const driver = require('./driver');
|
||||||
const formatProfilerEntry = require('../frontend/formatProfilerEntry');
|
const formatProfilerEntry = require('../frontend/formatProfilerEntry');
|
||||||
|
const formatProfilerChartEntry = require('../frontend/formatProfilerChartEntry');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
packageName: 'dbgate-plugin-mongo',
|
packageName: 'dbgate-plugin-mongo',
|
||||||
drivers: [driver],
|
drivers: [driver],
|
||||||
functions: {
|
functions: {
|
||||||
formatProfilerEntry,
|
formatProfilerEntry,
|
||||||
|
formatProfilerChartEntry,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ const driver = {
|
|||||||
supportsServerSummary: true,
|
supportsServerSummary: true,
|
||||||
supportsDatabaseProfiler: true,
|
supportsDatabaseProfiler: true,
|
||||||
profilerFormatterFunction: 'formatProfilerEntry@dbgate-plugin-mongo',
|
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',
|
databaseUrlPlaceholder: 'e.g. mongodb://username:password@mongodb.mydomain.net/dbname',
|
||||||
|
|
||||||
getQuerySplitterOptions: () => mongoSplitterOptions,
|
getQuerySplitterOptions: () => mongoSplitterOptions,
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -2,6 +2,7 @@ const _ = require('lodash');
|
|||||||
|
|
||||||
function formatProfilerEntry(obj) {
|
function formatProfilerEntry(obj) {
|
||||||
const ts = obj.ts;
|
const ts = obj.ts;
|
||||||
|
const stats = { millis: obj.millis };
|
||||||
let op = obj.op;
|
let op = obj.op;
|
||||||
let doc;
|
let doc;
|
||||||
let query;
|
let query;
|
||||||
@@ -64,6 +65,7 @@ function formatProfilerEntry(obj) {
|
|||||||
doc,
|
doc,
|
||||||
query,
|
query,
|
||||||
ext,
|
ext,
|
||||||
|
stats,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import driver from './driver';
|
import driver from './driver';
|
||||||
import formatProfilerEntry from './formatProfilerEntry';
|
import formatProfilerEntry from './formatProfilerEntry';
|
||||||
|
import formatProfilerChartEntry from './formatProfilerChartEntry';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
packageName: 'dbgate-plugin-mongo',
|
packageName: 'dbgate-plugin-mongo',
|
||||||
drivers: [driver],
|
drivers: [driver],
|
||||||
functions: {
|
functions: {
|
||||||
formatProfilerEntry,
|
formatProfilerEntry,
|
||||||
|
formatProfilerChartEntry,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user