All files orchestrate.js

85.42% Statements 41/48
72.73% Branches 8/11
100% Functions 2/2
85.42% Lines 41/48

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131    1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                   231x 231x 216x   15x                                     236x 236x     236x         236x         236x 236x         236x                           236x     236x 236x 236x                         236x 236x 236x 236x 236x 236x 236x 236x     236x                 236x 216x         236x     1x      
'use strict';
 
const normalize = require( '../function-schemata/javascript/src/normalize.js' );
const { makeMappedResultEnvelope, setMetadataValues, wrapInZ9, wrapInQuote } = require( '../function-schemata/javascript/src/utils.js' );
const { validatesAsFunctionCall } = require( '../function-schemata/javascript/src/schema.js' );
const { error, makeErrorInNormalForm } = require( '../function-schemata/javascript/src/error.js' );
const ErrorFormatter = require( '../function-schemata/javascript/src/errorFormatter.js' );
const { execute } = require( './execute.js' );
const { isError, makeWrappedResultEnvelope, safeJsonStringify, returnOnFirstError } = require( './utils.js' );
const { Invariants } = require( './Invariants.js' );
const { ZWrapper } = require( './ZWrapper.js' );
const sUtil = require( '../lib/util' );
const ImplementationSelector = require( './implementationSelector.js' );
const { cpuUsage, memoryUsage } = require( 'node:process' );
const os = require( 'os' );
 
/**
 * Returns the pair <original ZObject, Unit> if the input object is a Z7;
 * otherwise returns the pair <Unit, Z5>.
 *
 * @param {Object} zobject
 * @return {Object} a Z22 as described above
 */
function Z7OrError( zobject ) {
	const status = validatesAsFunctionCall( zobject );
	if ( status.isValid() ) {
		return makeMappedResultEnvelope( zobject, null );
	}
	return makeMappedResultEnvelope(
		null,
		makeErrorInNormalForm( error.object_type_mismatch, [ wrapInZ9( 'Z7' ), wrapInQuote( zobject ), status.getZ5() ] )
	);
}
 
/**
 * Main orchestration workflow. Executes an input Z7 and returns either the
 * results of function evaluation or the relevant error(s).
 *
 * Takes and returns JSON representation; not ZWrapper.
 *
 * @param {Object} zobject the function call
 * @param {Invariants} invariants encapsulates global orchestrator config and wrappers
 *      for evaluator and Wiki services
 * @param {ImplementationSelector} implementationSelector
 * @return {Object} a Z22 containing the result of function evaluation or a Z5 (in Z22K2/metadata)
 */
async function orchestrate( zobject, invariants, implementationSelector = null ) {
	const startTime = new Date();
	const startUsage = cpuUsage();
 
	let currentResponseEnvelope;
	Iif ( isError( zobject ) ) {
		currentResponseEnvelope = makeMappedResultEnvelope(
			null, zobject, /* canonicalize= */true
		);
	} else {
		currentResponseEnvelope = makeMappedResultEnvelope(
			zobject, null, /* canonicalize= */true
		);
	}
 
	const stringifiedResult = safeJsonStringify( zobject );
	invariants.logger.log(
		'info',
		{ message: 'Incoming orchestrator request', requestId: invariants.requestId, info: stringifiedResult }
	);
 
	const callTuples = [
		[ normalize, [ /* generically= */true, /* withVoid= */ true ], 'normalize' ],
		// TODO (T296685): Dereference top-level object if it is a Z9?
		[ Z7OrError, [], 'Z7OrError' ],
		[ makeWrappedResultEnvelope, [], 'wrapAsZObject' ],
		[
			execute, [
				invariants, /* doValidate= */invariants.orchestratorConfig.doValidate,
				/* implementationSelector= */implementationSelector,
				/* resolveInternals= */true, /* topLevel= */true ],
			'execute'
		]
	];
 
	Iif ( invariants.orchestratorConfig.generateFunctionsMetrics ) {
		sUtil.logHeapUsage( invariants.logger, invariants.requestId, 'during orchestrate()' );
	}
	try {
		currentResponseEnvelope = await returnOnFirstError( currentResponseEnvelope, callTuples );
		Iif ( invariants.orchestratorConfig.generateFunctionsMetrics ) {
			sUtil.logHeapUsage( invariants.logger, invariants.requestId, 'after orchestrate()' );
		}
	} catch ( e ) {
		const errorMessage = `Call tuples failed in returnOnFirstError. Error: ${ e }.`;
		invariants.logger.log( 'error', { message: errorMessage, requestId: invariants.requestId } );
		// The zobject provides context for a Z507/Evaluation error (and will be quoted there)
		const zerror = ErrorFormatter.wrapMessageInEvaluationError( errorMessage, zobject );
		// This currentResponseEnvelope will be JSON, not a ZWrapper.
		// makeMappedResultEnvelope will put zerror into a metadata map.
		currentResponseEnvelope = makeMappedResultEnvelope( null, zerror );
	}
 
	const cpuUsageStats = cpuUsage( startUsage );
	const cpuUsageStr = ( ( cpuUsageStats.user + cpuUsageStats.system ) / 1000 ) + ' ms';
	const memoryUsageStr = Math.round( memoryUsage.rss() / 1024 / 1024 * 100 ) / 100 + ' MiB';
	const endTime = new Date();
	const startTimeStr = startTime.toISOString();
	const endTimeStr = endTime.toISOString();
	const durationStr = ( endTime.getTime() - startTime.getTime() ) + ' ms';
	const hostname = os.hostname();
 
	// Note: Keep this block in sync with the 'standardMetaData' list in mswOrchestrateTest
	setMetadataValues( currentResponseEnvelope, [
		[ { Z1K1: 'Z6', Z6K1: 'orchestrationMemoryUsage' }, { Z1K1: 'Z6', Z6K1: memoryUsageStr } ],
		[ { Z1K1: 'Z6', Z6K1: 'orchestrationCpuUsage' }, { Z1K1: 'Z6', Z6K1: cpuUsageStr } ],
		[ { Z1K1: 'Z6', Z6K1: 'orchestrationStartTime' }, { Z1K1: 'Z6', Z6K1: startTimeStr } ],
		[ { Z1K1: 'Z6', Z6K1: 'orchestrationEndTime' }, { Z1K1: 'Z6', Z6K1: endTimeStr } ],
		[ { Z1K1: 'Z6', Z6K1: 'orchestrationDuration' }, { Z1K1: 'Z6', Z6K1: durationStr } ],
		[ { Z1K1: 'Z6', Z6K1: 'orchestrationHostname' }, { Z1K1: 'Z6', Z6K1: hostname } ]
	] );
 
	if ( currentResponseEnvelope instanceof ZWrapper ) {
		currentResponseEnvelope = currentResponseEnvelope.asJSON(
			invariants.orchestratorConfig.addNestedMetadata
		);
	}
 
	return currentResponseEnvelope;
}
 
module.exports = {
	orchestrate
};