mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-27 20:46:00 +00:00
mongodb profiler
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
3
packages/types/engines.d.ts
vendored
3
packages/types/engines.d.ts
vendored
@@ -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;
|
||||||
|
|||||||
@@ -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 &&
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
115
packages/web/src/tabs/ProfilerTab.svelte
Normal file
115
packages/web/src/tabs/ProfilerTab.svelte
Normal 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>
|
||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user