profiler charts

This commit is contained in:
Jan Prochazka
2022-12-18 12:29:21 +01:00
parent dbfdaafb86
commit 9a2631dc09
9 changed files with 178 additions and 20 deletions

View File

@@ -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,
};
},
};

View File

@@ -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;

View File

@@ -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,
},
});
},

View File

@@ -1,6 +1,4 @@
<script lang="ts" context="module">
export const matchingProps = ['conid', 'database', 'pureName', 'sql'];
const getCurrentEditor = () => getActiveComponent('ProfilerTab');
registerCommand({
@@ -50,18 +48,25 @@
import createActivator, { getActiveComponent } from '../utility/createActivator';
import { useConnectionInfo } from '../utility/metadataLoaders';
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 let conid;
export let database;
export let jslid;
export let formatterFunction;
export let profilerFormatterFunction;
export let profilerChartFormatterFunction;
export let profilerChartMeasures;
let profiling = false;
let sessionId;
let isLoadingChart = false;
let intervalId;
let chartData;
$: connection = useConnectionInfo({ conid });
$: engine = findEngineDriver($connection, $extensions);
@@ -74,6 +79,10 @@
});
}
}, 15 * 1000);
if (jslid) {
loadChart();
}
});
onDestroy(() => {
@@ -109,11 +118,37 @@
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;
apiCall('sessions/stop-profiler', { sesid: sessionId });
invalidateCommands();
loadChart();
}
export function stopProfilingEnabled() {
@@ -137,27 +172,76 @@
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>
<ToolStripContainer>
{#if jslid}
<JslDataGrid
{jslid}
listenInitializeFile
formatterFunction={formatterFunction || engine?.profilerFormatterFunction}
/>
<VerticalSplitter allowCollapseChild1 allowCollapseChild2>
<svelte:fragment slot="1">
<JslDataGrid
{jslid}
listenInitializeFile
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}
<ErrorInfo message="Profiler not yet started" alignTop />
{/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">
<ToolStripCommandButton command="profiler.start" />
<ToolStripCommandButton command="profiler.stop" />

View File

@@ -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,
},
};

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,
};
}

View File

@@ -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,
},
};