mongo profiler formatter

This commit is contained in:
Jan Prochazka 2022-12-17 12:34:28 +01:00
parent 34a4f9adbf
commit 123e00ecbc
10 changed files with 127 additions and 11 deletions

View File

@ -7,6 +7,7 @@ const DatastoreProxy = require('../utility/DatastoreProxy');
const { saveFreeTableData } = require('../utility/freeTableStorage'); const { saveFreeTableData } = require('../utility/freeTableStorage');
const getJslFileName = require('../utility/getJslFileName'); const getJslFileName = require('../utility/getJslFileName');
const JsonLinesDatastore = require('../utility/JsonLinesDatastore'); const JsonLinesDatastore = require('../utility/JsonLinesDatastore');
const requirePluginFunction = require('../utility/requirePluginFunction');
const socket = require('../utility/socket'); const socket = require('../utility/socket');
function readFirstLine(file) { function readFirstLine(file) {
@ -99,10 +100,11 @@ module.exports = {
// return readerInfo; // return readerInfo;
// }, // },
async ensureDatastore(jslid) { async ensureDatastore(jslid, formatterFunction) {
const rowFormatter = requirePluginFunction(formatterFunction);
let datastore = this.datastores[jslid]; let datastore = this.datastores[jslid];
if (!datastore) { if (!datastore) {
datastore = new JsonLinesDatastore(getJslFileName(jslid)); datastore = new JsonLinesDatastore(getJslFileName(jslid), rowFormatter);
// datastore = new DatastoreProxy(getJslFileName(jslid)); // datastore = new DatastoreProxy(getJslFileName(jslid));
this.datastores[jslid] = datastore; this.datastores[jslid] = datastore;
} }
@ -131,8 +133,8 @@ module.exports = {
}, },
getRows_meta: true, getRows_meta: true,
async getRows({ jslid, offset, limit, filters }) { async getRows({ jslid, offset, limit, filters, formatterFunction }) {
const datastore = await this.ensureDatastore(jslid); const datastore = await this.ensureDatastore(jslid, formatterFunction);
return datastore.getRows(offset, limit, _.isEmpty(filters) ? null : filters); return datastore.getRows(offset, limit, _.isEmpty(filters) ? null : filters);
}, },
@ -150,8 +152,8 @@ module.exports = {
}, },
loadFieldValues_meta: true, loadFieldValues_meta: true,
async loadFieldValues({ jslid, field, search }) { async loadFieldValues({ jslid, field, search, formatterFunction }) {
const datastore = await this.ensureDatastore(jslid); const datastore = await this.ensureDatastore(jslid, formatterFunction);
const res = new Set(); const res = new Set();
await datastore.enumRows(row => { await datastore.enumRows(row => {
if (!filterName(search, row[field])) return true; if (!filterName(search, row[field])) return true;

View File

@ -22,8 +22,9 @@ function fetchNextLineFromReader(reader) {
} }
class JsonLinesDatastore { class JsonLinesDatastore {
constructor(file) { constructor(file, rowFormatter) {
this.file = file; this.file = file;
this.rowFormatter = rowFormatter;
this.reader = null; this.reader = null;
this.readedDataRowCount = 0; this.readedDataRowCount = 0;
this.readedSchemaRow = false; this.readedSchemaRow = false;
@ -62,6 +63,11 @@ class JsonLinesDatastore {
); );
} }
parseLine(line) {
const res = JSON.parse(line);
return this.rowFormatter ? this.rowFormatter(res) : res;
}
async _readLine(parse) { async _readLine(parse) {
// if (this.firstRowToBeReturned) { // if (this.firstRowToBeReturned) {
// const res = this.firstRowToBeReturned; // const res = this.firstRowToBeReturned;
@ -84,14 +90,14 @@ class JsonLinesDatastore {
} }
} }
if (this.currentFilter) { if (this.currentFilter) {
const parsedLine = JSON.parse(line); const parsedLine = this.parseLine(line);
if (evaluateCondition(this.currentFilter, parsedLine)) { if (evaluateCondition(this.currentFilter, parsedLine)) {
this.readedDataRowCount += 1; this.readedDataRowCount += 1;
return parse ? parsedLine : true; return parse ? parsedLine : true;
} }
} else { } else {
this.readedDataRowCount += 1; this.readedDataRowCount += 1;
return parse ? JSON.parse(line) : true; return parse ? this.parseLine(line) : true;
} }
} }

View File

@ -0,0 +1,16 @@
const _ = require('lodash');
const requirePlugin = require('../shell/requirePlugin');
function requirePluginFunction(functionName) {
if (!functionName) return null;
if (functionName.includes('@')) {
const [shortName, packageName] = functionName.split('@');
const plugin = requirePlugin(packageName);
if (plugin.functions) {
return plugin.functions[shortName];
}
}
return null;
}
module.exports = requirePluginFunction;

View File

@ -78,6 +78,7 @@ export interface EngineDriver {
supportsDatabaseDump?: boolean; supportsDatabaseDump?: boolean;
supportsServerSummary?: boolean; supportsServerSummary?: boolean;
supportsDatabaseProfiler?: boolean; supportsDatabaseProfiler?: boolean;
profilerFormatterFunction?: string;
isElectronOnly?: boolean; isElectronOnly?: boolean;
supportedCreateDatabase?: boolean; supportedCreateDatabase?: boolean;
showConnectionField?: (field: string, values: any) => boolean; showConnectionField?: (field: string, values: any) => boolean;

View File

@ -12,12 +12,13 @@
}); });
async function loadDataPage(props, offset, limit) { async function loadDataPage(props, offset, limit) {
const { jslid, display } = props; const { jslid, display, formatterFunction } = props;
const response = await apiCall('jsldata/get-rows', { const response = await apiCall('jsldata/get-rows', {
jslid, jslid,
offset, offset,
limit, limit,
formatterFunction,
filters: display ? display.compileFilters() : null, filters: display ? display.compileFilters() : null,
}); });
@ -34,6 +35,9 @@
const response = await apiCall('jsldata/get-stats', { jslid }); const response = await apiCall('jsldata/get-stats', { jslid });
return response.rowCount; return response.rowCount;
} }
export let formatterPlugin;
export let formatterFunction;
</script> </script>
<script lang="ts"> <script lang="ts">
@ -56,6 +60,7 @@
export let jslid; export let jslid;
export let display; export let display;
export let formatterFunction;
export const activator = createActivator('JslDataGridCore', false); export const activator = createActivator('JslDataGridCore', false);

View File

@ -32,6 +32,8 @@
</script> </script>
<script> <script>
import { findEngineDriver } from 'dbgate-tools';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte'; import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
@ -46,6 +48,8 @@
import { currentArchive, selectedWidget } from '../stores'; import { currentArchive, selectedWidget } from '../stores';
import { apiCall } from '../utility/api'; import { apiCall } from '../utility/api';
import createActivator, { getActiveComponent } from '../utility/createActivator'; import createActivator, { getActiveComponent } from '../utility/createActivator';
import { useConnectionInfo } from '../utility/metadataLoaders';
import { extensions } from '../stores';
export const activator = createActivator('ProfilerTab', true); export const activator = createActivator('ProfilerTab', true);
@ -58,6 +62,9 @@
let intervalId; let intervalId;
$: connection = useConnectionInfo({ conid });
$: engine = findEngineDriver($connection, $extensions);
onMount(() => { onMount(() => {
intervalId = setInterval(() => { intervalId = setInterval(() => {
if (sessionId) { if (sessionId) {
@ -125,7 +132,7 @@
<ToolStripContainer> <ToolStripContainer>
{#if jslid} {#if jslid}
<JslDataGrid {jslid} listenInitializeFile /> <JslDataGrid {jslid} listenInitializeFile formatterFunction={engine?.profilerFormatterFunction} />
{:else} {:else}
<ErrorInfo message="Profiler not yet started" alignTop /> <ErrorInfo message="Profiler not yet started" alignTop />
{/if} {/if}

View File

@ -1,6 +1,10 @@
const driver = require('./driver'); const driver = require('./driver');
const formatProfilerEntry = require('../frontend/formatProfilerEntry');
module.exports = { module.exports = {
packageName: 'dbgate-plugin-mongo', packageName: 'dbgate-plugin-mongo',
drivers: [driver], drivers: [driver],
functions: {
formatProfilerEntry,
},
}; };

View File

@ -34,6 +34,7 @@ const driver = {
supportsDatabaseUrl: true, supportsDatabaseUrl: true,
supportsServerSummary: true, supportsServerSummary: true,
supportsDatabaseProfiler: true, supportsDatabaseProfiler: true,
profilerFormatterFunction: 'formatProfilerEntry@dbgate-plugin-mongo',
databaseUrlPlaceholder: 'e.g. mongodb://username:password@mongodb.mydomain.net/dbname', databaseUrlPlaceholder: 'e.g. mongodb://username:password@mongodb.mydomain.net/dbname',
getQuerySplitterOptions: () => mongoSplitterOptions, getQuerySplitterOptions: () => mongoSplitterOptions,

View File

@ -0,0 +1,70 @@
const _ = require('lodash');
function formatProfilerEntry(obj) {
const ts = obj.ts;
let op = obj.op;
let doc;
let query;
let ext;
if (op == 'query') {
const cmd = obj.command || obj.query;
doc = cmd.find;
query = cmd.filter;
ext = _.pick(cmd, ['sort', 'limit', 'skip']);
} else if (op == 'update') {
doc = obj.ns.split('.').slice(-1)[0];
query = obj.command && obj.command.q;
ext = _.pick(obj, ['nModified', 'nMatched']);
} else if (op == 'insert') {
doc = obj.ns.split('.').slice(-1)[0];
ext = _.pick(obj, ['ninserted']);
} else if (op == 'remove') {
doc = obj.ns.split('.').slice(-1)[0];
query = obj.command && obj.command.q;
} else if (op == 'command' && obj.command) {
const cmd = obj.command;
if (cmd.count) {
op = 'count';
query = cmd.query;
} else if (cmd.aggregate) {
op = 'aggregate';
query = cmd.pipeline;
} else if (cmd.distinct) {
op = 'distinct';
query = cmd.query;
ext = _.pick(cmd, ['key']);
} else if (cmd.drop) {
op = 'drop';
} else if (cmd.findandmodify) {
op = 'findandmodify';
query = cmd.query;
ext = _.pick(cmd, ['sort', 'update', 'remove', 'fields', 'upsert', 'new']);
} else if (cmd.group) {
op = 'group';
doc = cmd.group.ns;
ext = _.pick(cmd, ['key', 'initial', 'cond', '$keyf', '$reduce', 'finalize']);
} else if (cmd.map) {
op = 'map';
doc = cmd.mapreduce;
query = _.omit(cmd, ['mapreduce', 'map', 'reduce']);
ext = { map: cmd.map, reduce: cmd.reduce };
} else {
// unknown command
op = 'unknown';
query = obj;
}
} else {
// unknown operation
query = obj;
}
return {
ts,
op,
doc,
query,
ext,
};
}
module.exports = formatProfilerEntry;

View File

@ -1,6 +1,10 @@
import driver from './driver'; import driver from './driver';
import formatProfilerEntry from './formatProfilerEntry';
export default { export default {
packageName: 'dbgate-plugin-mongo', packageName: 'dbgate-plugin-mongo',
drivers: [driver], drivers: [driver],
functions: {
formatProfilerEntry,
},
}; };