mongodb profiler

This commit is contained in:
Jan Prochazka
2022-12-16 14:52:49 +01:00
parent c1ba758b01
commit 0e819bcc45
9 changed files with 236 additions and 2 deletions

View File

@@ -150,6 +150,31 @@ module.exports = {
return true; return true;
}, },
startProfiler_meta: true,
async startProfiler({ sesid }) {
const jslid = uuidv1();
const session = this.opened.find(x => x.sesid == sesid);
if (!session) {
throw new Error('Invalid session');
}
console.log(`Starting profiler, sesid=${sesid}`);
session.loadingReader_jslid = jslid;
session.subprocess.send({ msgtype: 'startProfiler', jslid });
return { state: 'ok', jslid };
},
stopProfiler_meta: true,
async stopProfiler({ sesid }) {
const session = this.opened.find(x => x.sesid == sesid);
if (!session) {
throw new Error('Invalid session');
}
session.subprocess.send({ msgtype: 'stopProfiler' });
return { state: 'ok' };
},
// cancel_meta: true, // cancel_meta: true,
// async cancel({ sesid }) { // async cancel({ sesid }) {
// const session = this.opened.find((x) => x.sesid == sesid); // const session = this.opened.find((x) => x.sesid == sesid);

View File

@@ -16,6 +16,7 @@ let storedConnection;
let afterConnectCallbacks = []; let afterConnectCallbacks = [];
// let currentHandlers = []; // let currentHandlers = [];
let lastPing = null; let lastPing = null;
let currentProfiler = null;
class TableWriter { class TableWriter {
constructor() { constructor() {
@@ -210,6 +211,31 @@ function waitConnected() {
}); });
} }
async function handleStartProfiler({ jslid }) {
await waitConnected();
const driver = requireEngineDriver(storedConnection);
if (!allowExecuteCustomScript(driver)) {
process.send({ msgtype: 'done' });
return;
}
const writer = new TableWriter();
writer.initializeFromReader(jslid);
currentProfiler = await driver.startProfiler(systemConnection, {
row: data => writer.rowFromReader(data),
});
currentProfiler.writer = writer;
}
async function handleStopProfiler({ jslid }) {
const driver = requireEngineDriver(storedConnection);
currentProfiler.writer.close();
driver.stopProfiler(systemConnection, currentProfiler);
currentProfiler = null;
}
async function handleExecuteQuery({ sql }) { async function handleExecuteQuery({ sql }) {
await waitConnected(); await waitConnected();
const driver = requireEngineDriver(storedConnection); const driver = requireEngineDriver(storedConnection);
@@ -280,6 +306,8 @@ const messageHandlers = {
connect: handleConnect, connect: handleConnect,
executeQuery: handleExecuteQuery, executeQuery: handleExecuteQuery,
executeReader: handleExecuteReader, executeReader: handleExecuteReader,
startProfiler: handleStartProfiler,
stopProfiler: handleStopProfiler,
ping: handlePing, ping: handlePing,
// cancel: handleCancel, // cancel: handleCancel,
}; };

View File

@@ -77,6 +77,7 @@ export interface EngineDriver {
supportsDatabaseUrl?: boolean; supportsDatabaseUrl?: boolean;
supportsDatabaseDump?: boolean; supportsDatabaseDump?: boolean;
supportsServerSummary?: boolean; supportsServerSummary?: boolean;
supportsDatabaseProfiler?: boolean;
isElectronOnly?: boolean; isElectronOnly?: boolean;
supportedCreateDatabase?: boolean; supportedCreateDatabase?: boolean;
showConnectionField?: (field: string, values: any) => boolean; showConnectionField?: (field: string, values: any) => boolean;
@@ -130,6 +131,8 @@ export interface EngineDriver {
callMethod(pool, method, args); callMethod(pool, method, args);
serverSummary(pool): Promise<ServerSummary>; serverSummary(pool): Promise<ServerSummary>;
summaryCommand(pool, command, row): Promise<void>; summaryCommand(pool, command, row): Promise<void>;
startProfiler(pool, options): Promise<any>;
stopProfiler(pool, profiler): Promise<void>;
analyserClass?: any; analyserClass?: any;
dumperClass?: any; dumperClass?: any;

View File

@@ -254,6 +254,18 @@
}); });
}; };
const handleDatabaseProfiler = () => {
openNewTab({
title: 'Profiler',
icon: 'img profiler',
tabComponent: 'ProfilerTab',
props: {
conid: connection._id,
database: name,
},
});
};
async function handleConfirmSql(sql) { async function handleConfirmSql(sql) {
saveScriptToDatabase({ conid: connection._id, database: name }, sql, false); saveScriptToDatabase({ conid: connection._id, database: name }, sql, false);
} }
@@ -284,7 +296,8 @@
!connection.singleDatabase && { onClick: handleDropDatabase, text: 'Drop database' }, !connection.singleDatabase && { onClick: handleDropDatabase, text: 'Drop database' },
{ divider: true }, { divider: true },
driver?.databaseEngineTypes?.includes('sql') && { onClick: handleShowDiagram, text: 'Show diagram' }, driver?.databaseEngineTypes?.includes('sql') && { onClick: handleShowDiagram, text: 'Show diagram' },
isSqlOrDoc && { onClick: handleSqlGenerator, text: 'SQL Generator' }, driver?.databaseEngineTypes?.includes('sql') && { onClick: handleSqlGenerator, text: 'SQL Generator' },
driver?.supportsDatabaseProfiler && { onClick: handleDatabaseProfiler, text: 'Database profiler' },
isSqlOrDoc && { onClick: handleOpenJsonModel, text: 'Open model as JSON' }, isSqlOrDoc && { onClick: handleOpenJsonModel, text: 'Open model as JSON' },
isSqlOrDoc && { onClick: handleExportModel, text: 'Export DB model - experimental' }, isSqlOrDoc && { onClick: handleExportModel, text: 'Export DB model - experimental' },
isSqlOrDoc && isSqlOrDoc &&

View File

@@ -49,6 +49,9 @@
'icon close': 'mdi mdi-close', 'icon close': 'mdi mdi-close',
'icon unsaved': 'mdi mdi-record', 'icon unsaved': 'mdi mdi-record',
'icon stop': 'mdi mdi-close-octagon', 'icon stop': 'mdi mdi-close-octagon',
'icon play': 'mdi mdi-play',
'icon play-stop': 'mdi mdi-stop',
'icon pause': 'mdi mdi-pause',
'icon filter': 'mdi mdi-filter', 'icon filter': 'mdi mdi-filter',
'icon filter-off': 'mdi mdi-filter-off', 'icon filter-off': 'mdi mdi-filter-off',
'icon reload': 'mdi mdi-reload', 'icon reload': 'mdi mdi-reload',
@@ -176,6 +179,7 @@
'img app-command': 'mdi mdi-flash color-icon-green', 'img app-command': 'mdi mdi-flash color-icon-green',
'img app-query': 'mdi mdi-view-comfy color-icon-magenta', 'img app-query': 'mdi mdi-view-comfy color-icon-magenta',
'img connection': 'mdi mdi-connection color-icon-blue', 'img connection': 'mdi mdi-connection color-icon-blue',
'img profiler': 'mdi mdi-gauge color-icon-blue',
'img add': 'mdi mdi-plus-circle color-icon-green', 'img add': 'mdi mdi-plus-circle color-icon-green',
'img minus': 'mdi mdi-minus-circle color-icon-red', 'img minus': 'mdi mdi-minus-circle color-icon-red',

View File

@@ -0,0 +1,115 @@
<script lang="ts" context="module">
export const matchingProps = ['conid', 'database', 'pureName', 'sql'];
const getCurrentEditor = () => getActiveComponent('ProfilerTab');
registerCommand({
id: 'profiler.start',
category: 'Profiler',
name: 'Start profiling',
icon: 'icon play',
testEnabled: () => getCurrentEditor() && !getCurrentEditor()?.isProfiling(),
onClick: () => getCurrentEditor().startProfiling(),
});
registerCommand({
id: 'profiler.stop',
category: 'Profiler',
name: 'Stop profiling',
icon: 'icon play-stop',
testEnabled: () => getCurrentEditor()?.isProfiling(),
onClick: () => getCurrentEditor().stopProfiling(),
});
</script>
<script>
import { onDestroy, onMount } from 'svelte';
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
import invalidateCommands from '../commands/invalidateCommands';
import registerCommand from '../commands/registerCommand';
import JslDataGrid from '../datagrid/JslDataGrid.svelte';
import ErrorInfo from '../elements/ErrorInfo.svelte';
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
import { apiCall } from '../utility/api';
import createActivator, { getActiveComponent } from '../utility/createActivator';
export const activator = createActivator('ProfilerTab', true);
export let conid;
export let database;
let profiling = false;
let jslid;
let sessionId;
let intervalId;
onMount(() => {
intervalId = setInterval(() => {
if (sessionId) {
apiCall('sessions/ping', {
sesid: sessionId,
});
}
}, 15 * 1000);
});
onDestroy(() => {
clearInterval(intervalId);
});
export function isProfiling() {
return profiling;
}
export async function startProfiling() {
profiling = true;
let sesid = sessionId;
if (!sesid) {
const resp = await apiCall('sessions/create', {
conid,
database,
});
sesid = resp.sesid;
sessionId = sesid;
}
const resp = await apiCall('sessions/start-profiler', {
sesid,
});
jslid = resp.jslid;
invalidateCommands();
}
export function stopProfiling() {
profiling = false;
apiCall('sessions/stop-profiler', { sesid: sessionId });
invalidateCommands();
}
</script>
<ToolStripContainer>
{#if jslid}
<JslDataGrid {jslid} listenInitializeFile />
{: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" />
</svelte:fragment>
</ToolStripContainer>

View File

@@ -27,6 +27,7 @@ import * as ConnectionTab from './ConnectionTab.svelte';
import * as MapTab from './MapTab.svelte'; import * as MapTab from './MapTab.svelte';
import * as PerspectiveTab from './PerspectiveTab.svelte'; import * as PerspectiveTab from './PerspectiveTab.svelte';
import * as ServerSummaryTab from './ServerSummaryTab.svelte'; import * as ServerSummaryTab from './ServerSummaryTab.svelte';
import * as ProfilerTab from './ProfilerTab.svelte';
export default { export default {
TableDataTab, TableDataTab,
@@ -58,4 +59,5 @@ export default {
MapTab, MapTab,
PerspectiveTab, PerspectiveTab,
ServerSummaryTab, ServerSummaryTab,
ProfilerTab,
}; };

View File

@@ -38,7 +38,7 @@ async function getScriptableDb(pool) {
const db = pool.__getDatabase(); const db = pool.__getDatabase();
const collections = await db.listCollections().toArray(); const collections = await db.listCollections().toArray();
for (const collection of collections) { for (const collection of collections) {
db[collection.name] = db.collection(collection.name); _.set(db, collection.name, db.collection(collection.name));
} }
return db; return db;
} }
@@ -165,6 +165,49 @@ const driver = {
options.done(); options.done();
}, },
async startProfiler(pool, options) {
const db = await getScriptableDb(pool);
const old = await db.command({ profile: -1 });
await db.command({ profile: 2 });
const cursor = await db.collection('system.profile').find({
ns: /^((?!(admin\.\$cmd|\.system|\.tmp\.)).)*$/,
ts: { $gt: new Date() },
'command.profile': { $exists: false },
'command.collStats': { $exists: false },
'command.collstats': { $exists: false },
'command.createIndexes': { $exists: false },
'command.listIndexes': { $exists: false },
// "command.cursor": {"$exists": false},
'command.create': { $exists: false },
'command.dbstats': { $exists: false },
'command.scale': { $exists: false },
'command.explain': { $exists: false },
'command.killCursors': { $exists: false },
'command.count': { $ne: 'system.profile' },
op: /^((?!(getmore|killcursors)).)/i,
});
cursor.addCursorFlag('tailable', true);
cursor.addCursorFlag('awaitData', true);
cursor
.forEach((row) => {
// console.log('ROW', row);
options.row(row);
})
.catch((err) => {
console.error('Cursor stopped with error:', err.message);
});
return {
cursor,
old,
};
},
async stopProfiler(pool, { cursor, old }) {
cursor.close();
const db = await getScriptableDb(pool);
await db.command({ profile: old.was, slowms: old.slowms });
},
async readQuery(pool, sql, structure) { async readQuery(pool, sql, structure) {
try { try {
const json = JSON.parse(sql); const json = JSON.parse(sql);

View File

@@ -33,6 +33,7 @@ const driver = {
defaultPort: 27017, defaultPort: 27017,
supportsDatabaseUrl: true, supportsDatabaseUrl: true,
supportsServerSummary: true, supportsServerSummary: true,
supportsDatabaseProfiler: true,
databaseUrlPlaceholder: 'e.g. mongodb://username:password@mongodb.mydomain.net/dbname', databaseUrlPlaceholder: 'e.g. mongodb://username:password@mongodb.mydomain.net/dbname',
getQuerySplitterOptions: () => mongoSplitterOptions, getQuerySplitterOptions: () => mongoSplitterOptions,