|
@@ -6,7 +6,7 @@
|
|
|
|
|
|
import { UUID } from 'mol-util';
|
|
|
import { getQueryByName, normalizeQueryParams, QueryDefinition } from './api';
|
|
|
-import { getStructure } from './structure-wrapper';
|
|
|
+import { getStructure, StructureWrapper } from './structure-wrapper';
|
|
|
import Config from '../config';
|
|
|
import { Progress, now } from 'mol-task';
|
|
|
import { ConsoleLogger } from 'mol-util/console-logger';
|
|
@@ -15,6 +15,9 @@ import * as Encoder from 'mol-io/writer/cif'
|
|
|
import { encode_mmCIF_categories } from 'mol-model/structure/export/mmcif';
|
|
|
import { Selection } from 'mol-model/structure';
|
|
|
import Version from '../version'
|
|
|
+import { Column } from 'mol-data/db';
|
|
|
+import { Iterator } from 'mol-data';
|
|
|
+import { PerformanceMonitor } from 'mol-util/performance-monitor';
|
|
|
|
|
|
export interface ResponseFormat {
|
|
|
isBinary: boolean
|
|
@@ -22,6 +25,7 @@ export interface ResponseFormat {
|
|
|
|
|
|
export interface Request {
|
|
|
id: UUID,
|
|
|
+ datetime_utc: string,
|
|
|
|
|
|
sourceId: '_local_' | string,
|
|
|
entryId: string,
|
|
@@ -31,6 +35,12 @@ export interface Request {
|
|
|
responseFormat: ResponseFormat
|
|
|
}
|
|
|
|
|
|
+export interface Stats {
|
|
|
+ structure: StructureWrapper,
|
|
|
+ queryTimeMs: number,
|
|
|
+ encodeTimeMs: number
|
|
|
+}
|
|
|
+
|
|
|
export function createRequest(sourceId: '_local_' | string, entryId: string, queryName: string, params: any): Request {
|
|
|
const queryDefinition = getQueryByName(queryName);
|
|
|
if (!queryDefinition) throw new Error(`Query '${queryName}' is not supported.`);
|
|
@@ -39,6 +49,7 @@ export function createRequest(sourceId: '_local_' | string, entryId: string, que
|
|
|
|
|
|
return {
|
|
|
id: UUID.create(),
|
|
|
+ datetime_utc: `${new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')}`,
|
|
|
sourceId,
|
|
|
entryId,
|
|
|
queryDefinition,
|
|
@@ -47,24 +58,42 @@ export function createRequest(sourceId: '_local_' | string, entryId: string, que
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+const perf = new PerformanceMonitor();
|
|
|
+
|
|
|
export async function resolveRequest(req: Request, writer: Writer) {
|
|
|
ConsoleLogger.logId(req.id, 'Query', 'Starting.');
|
|
|
|
|
|
const wrappedStructure = await getStructure(req.sourceId, req.entryId);
|
|
|
+
|
|
|
+ perf.start('query');
|
|
|
const structure = req.queryDefinition.structureTransform
|
|
|
? await req.queryDefinition.structureTransform(req.normalizedParams, wrappedStructure.structure)
|
|
|
: wrappedStructure.structure;
|
|
|
const query = req.queryDefinition.query(req.normalizedParams, structure);
|
|
|
const result = Selection.unionStructure(await query(structure).run(abortingObserver, 250));
|
|
|
+ perf.end('query');
|
|
|
|
|
|
ConsoleLogger.logId(req.id, 'Query', 'Query finished.');
|
|
|
|
|
|
const encoder = Encoder.create({ binary: req.responseFormat.isBinary, encoderName: `ModelServer ${Version}` });
|
|
|
+
|
|
|
+ perf.start('encode');
|
|
|
encoder.startDataBlock('result');
|
|
|
+ encoder.writeCategory(_model_server_result, [req]);
|
|
|
+ encoder.writeCategory(_model_server_params, [req]);
|
|
|
encode_mmCIF_categories(encoder, result);
|
|
|
+ perf.end('encode');
|
|
|
|
|
|
ConsoleLogger.logId(req.id, 'Query', 'Encoded.');
|
|
|
|
|
|
+ const stats: Stats = {
|
|
|
+ structure: wrappedStructure,
|
|
|
+ queryTimeMs: perf.time('query'),
|
|
|
+ encodeTimeMs: perf.time('encode')
|
|
|
+ };
|
|
|
+
|
|
|
+ encoder.writeCategory(_model_server_stats, [stats]);
|
|
|
+
|
|
|
encoder.writeTo(writer);
|
|
|
|
|
|
ConsoleLogger.logId(req.id, 'Query', 'Written.');
|
|
@@ -75,4 +104,72 @@ export function abortingObserver(p: Progress) {
|
|
|
if (now() - p.root.progress.startedTime > maxTime) {
|
|
|
p.requestAbort(`Exceeded maximum allowed time for a query (${maxTime}ms)`);
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+type FieldDesc<T> = Encoder.FieldDefinition<number, T>
|
|
|
+type CategoryInstance = Encoder.CategoryInstance
|
|
|
+
|
|
|
+function string<T>(name: string, str: (data: T, i: number) => string, isSpecified?: (data: T) => boolean): FieldDesc<T> {
|
|
|
+ if (isSpecified) {
|
|
|
+ return { name, type: Encoder.FieldType.Str, value: (i, d) => str(d, i), valueKind: (i, d) => isSpecified(d) ? Column.ValueKind.Present : Column.ValueKind.NotPresent };
|
|
|
+ }
|
|
|
+ return { name, type: Encoder.FieldType.Str, value: (i, d) => str(d, i) };
|
|
|
+}
|
|
|
+
|
|
|
+function int32<T>(name: string, value: (data: T) => number): FieldDesc<T> {
|
|
|
+ return { name, type: Encoder.FieldType.Int, value: (i, d) => value(d) };
|
|
|
+}
|
|
|
+
|
|
|
+const _model_server_result_fields: FieldDesc<Request>[] = [
|
|
|
+ string<Request>('request_id', ctx => '' + ctx.id),
|
|
|
+ string<Request>('datetime_utc', ctx => ctx.datetime_utc),
|
|
|
+ string<Request>('server_version', ctx => Version),
|
|
|
+ string<Request>('query_name', ctx => ctx.queryDefinition.name),
|
|
|
+ string<Request>('source_id', ctx => ctx.sourceId),
|
|
|
+ string<Request>('entry_id', ctx => ctx.entryId),
|
|
|
+];
|
|
|
+
|
|
|
+const _model_server_params_fields: FieldDesc<string[]>[] = [
|
|
|
+ string<string[]>('name', (ctx, i) => ctx[i][0]),
|
|
|
+ string<string[]>('value', (ctx, i) => ctx[i][1])
|
|
|
+];
|
|
|
+
|
|
|
+const _model_server_stats_fields: FieldDesc<Stats>[] = [
|
|
|
+ int32<Stats>('io_time_ms', ctx => ctx.structure.info.readTime | 0),
|
|
|
+ int32<Stats>('parse_time_ms', ctx => ctx.structure.info.parseTime | 0),
|
|
|
+ int32<Stats>('create_model_time_ms', ctx => ctx.structure.info.createModelTime | 0),
|
|
|
+ int32<Stats>('query_time_ms', ctx => ctx.queryTimeMs | 0),
|
|
|
+ int32<Stats>('encode_time_ms', ctx => ctx.encodeTimeMs | 0)
|
|
|
+];
|
|
|
+
|
|
|
+
|
|
|
+function _model_server_result(request: Request): CategoryInstance {
|
|
|
+ return {
|
|
|
+ data: request,
|
|
|
+ definition: { name: 'model_server_result', fields: _model_server_result_fields },
|
|
|
+ keys: () => Iterator.Value(0),
|
|
|
+ rowCount: 1
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+function _model_server_params(request: Request): CategoryInstance {
|
|
|
+ const params: string[][] = [];
|
|
|
+ for (const k of Object.keys(request.normalizedParams)) {
|
|
|
+ params.push([k, '' + request.normalizedParams[k]]);
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ data: params,
|
|
|
+ definition: { name: 'model_server_params', fields: _model_server_params_fields },
|
|
|
+ keys: () => Iterator.Range(0, params.length - 1),
|
|
|
+ rowCount: params.length
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+function _model_server_stats(stats: Stats): CategoryInstance {
|
|
|
+ return {
|
|
|
+ data: stats,
|
|
|
+ definition: { name: 'model_server_stats', fields: _model_server_stats_fields },
|
|
|
+ keys: () => Iterator.Value(0),
|
|
|
+ rowCount: 1
|
|
|
+ };
|
|
|
}
|