config.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /**
  2. * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import * as argparse from 'argparse';
  8. import { ObjectKeys } from '../../mol-util/type-helpers';
  9. import { VOLUME_SERVER_HEADER } from './server/version';
  10. import * as fs from 'fs';
  11. const DefaultServerConfig = {
  12. apiPrefix: '/VolumeServer',
  13. defaultPort: 1337,
  14. shutdownTimeoutMinutes: 24 * 60, /* a day */
  15. shutdownTimeoutVarianceMinutes: 60,
  16. idMap: [] as [string, string][]
  17. };
  18. function addLimitsArgs(parser: argparse.ArgumentParser) {
  19. parser.add_argument('--maxRequestBlockCount', {
  20. default: DefaultLimitsConfig.maxRequestBlockCount,
  21. metavar: 'COUNT',
  22. help: `Maximum number of blocks that could be read in 1 query.
  23. This is somewhat tied to the maxOutputSizeInVoxelCountByPrecisionLevel
  24. in that the <maximum number of voxel> = maxRequestBlockCount * <block size>^3.
  25. The default block size is 96 which corresponds to 28,311,552 voxels with 32 max blocks.`
  26. });
  27. parser.add_argument('--maxFractionalBoxVolume', {
  28. default: DefaultLimitsConfig.maxFractionalBoxVolume,
  29. metavar: 'VOLUME',
  30. help: `The maximum fractional volume of the query box (to prevent queries that are too big).`
  31. });
  32. parser.add_argument('--maxOutputSizeInVoxelCountByPrecisionLevel', {
  33. nargs: '+',
  34. default: DefaultLimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel,
  35. metavar: 'LEVEL',
  36. help: `What is the (approximate) maximum desired size in voxel count by precision level
  37. Rule of thumb: <response gzipped size> in [<voxel count> / 8, <voxel count> / 4].
  38. The maximum number of voxels is tied to maxRequestBlockCount.`
  39. });
  40. }
  41. function addServerArgs(parser: argparse.ArgumentParser) {
  42. parser.add_argument('--apiPrefix', {
  43. default: DefaultServerConfig.apiPrefix,
  44. metavar: 'PREFIX',
  45. help: `Specify the prefix of the API, i.e. <host>/<apiPrefix>/<API queries>`
  46. });
  47. parser.add_argument('--defaultPort', {
  48. default: DefaultServerConfig.defaultPort,
  49. metavar: 'PORT',
  50. type: 'int',
  51. help: `Specify the port the server is running on`
  52. });
  53. parser.add_argument('--shutdownTimeoutMinutes', {
  54. default: DefaultServerConfig.shutdownTimeoutMinutes,
  55. metavar: 'TIME',
  56. type: 'int',
  57. help: `0 for off, server will shut down after this amount of minutes.`
  58. });
  59. parser.add_argument('--shutdownTimeoutVarianceMinutes', {
  60. default: DefaultServerConfig.shutdownTimeoutVarianceMinutes,
  61. metavar: 'VARIANCE',
  62. type: 'int',
  63. help: `modifies the shutdown timer by +/- timeoutVarianceMinutes (to avoid multiple instances shutting at the same time)`
  64. });
  65. parser.add_argument('--idMap', {
  66. nargs: 2,
  67. action: 'append',
  68. metavar: ['TYPE', 'PATH'] as any,
  69. help: [
  70. 'Map `id`s for a `type` to a file path.',
  71. 'Example: x-ray \'../../data/mdb/xray/${id}-ccp4.mdb\'',
  72. '',
  73. ' - JS expressions can be used inside ${}, e.g. \'${id.substr(1, 2)}/${id}.mdb\'',
  74. ' - Can be specified multiple times.',
  75. ' - The `TYPE` variable (e.g. `x-ray`) is arbitrary and depends on how you plan to use the server.',
  76. ' By default, Mol* Viewer uses `x-ray` and `em`, but any particular use case may vary. '
  77. ].join('\n'),
  78. });
  79. }
  80. function addJsonConfigArgs(parser: argparse.ArgumentParser) {
  81. parser.add_argument('--cfg', {
  82. help: [
  83. 'JSON config file path',
  84. 'If a property is not specified, cmd line param/OS variable/default value are used.'
  85. ].join('\n'),
  86. required: false,
  87. });
  88. parser.add_argument('--printCfg', { help: 'Print current config for validation and exit.', required: false, action: 'store_true' });
  89. parser.add_argument('--cfgTemplate', { help: 'Prints default JSON config template to be modified and exits.', required: false, action: 'store_true' });
  90. }
  91. export interface ServerJsonConfig {
  92. cfg?: string,
  93. printCfg?: any,
  94. cfgTemplate?: any
  95. }
  96. export type ServerConfig = typeof DefaultServerConfig
  97. export const ServerConfig = { ...DefaultServerConfig };
  98. function setServerConfig(config: ServerConfig) {
  99. for (const k of ObjectKeys(ServerConfig)) {
  100. if (config[k] !== void 0) (ServerConfig as any)[k] = config[k];
  101. }
  102. }
  103. function validateServerConfig() {
  104. if (!ServerConfig.idMap || ServerConfig.idMap.length === 0) {
  105. throw new Error(`Please provide 'idMap' configuration. See [-h] for available options.`);
  106. }
  107. }
  108. const DefaultLimitsConfig = {
  109. maxRequestBlockCount: 32,
  110. maxFractionalBoxVolume: 1024,
  111. maxOutputSizeInVoxelCountByPrecisionLevel: [
  112. 0.5 * 1024 * 1024, // ~ 80*80*80
  113. 1 * 1024 * 1024,
  114. 2 * 1024 * 1024,
  115. 4 * 1024 * 1024,
  116. 8 * 1024 * 1024,
  117. 16 * 1024 * 1024, // ~ 256*256*256
  118. 24 * 1024 * 1024
  119. ]
  120. };
  121. export type LimitsConfig = typeof DefaultLimitsConfig
  122. export const LimitsConfig = { ...DefaultLimitsConfig };
  123. function setLimitsConfig(config: LimitsConfig) {
  124. for (const k of ObjectKeys(LimitsConfig)) {
  125. if (config[k] !== void 0) (LimitsConfig as any)[k] = config[k];
  126. }
  127. }
  128. type FullServerConfig = ServerConfig & LimitsConfig
  129. function setConfig(config: FullServerConfig) {
  130. setServerConfig(config);
  131. setLimitsConfig(config);
  132. }
  133. const ServerConfigTemplate: FullServerConfig = {
  134. ...DefaultServerConfig,
  135. idMap: [
  136. ['x-ray', './path-to-xray-data/${id.substr(1, 2)}/${id}.mdb'],
  137. ['em', './path-to-em-data/emd-${id}.mdb']
  138. ] as [string, string][],
  139. ...DefaultLimitsConfig
  140. };
  141. export function configureServer() {
  142. const parser = new argparse.ArgumentParser({
  143. // version: VOLUME_SERVER_VERSION,
  144. add_help: true,
  145. description: VOLUME_SERVER_HEADER
  146. });
  147. addJsonConfigArgs(parser);
  148. addServerArgs(parser);
  149. addLimitsArgs(parser);
  150. const config = parser.parse_args() as FullServerConfig & ServerJsonConfig;
  151. if (!!config.cfgTemplate) {
  152. console.log(JSON.stringify(ServerConfigTemplate, null, 2));
  153. process.exit(0);
  154. }
  155. try {
  156. setConfig(config); // sets the config for global use
  157. if (config.cfg) {
  158. const cfg = JSON.parse(fs.readFileSync(config.cfg, 'utf8')) as FullServerConfig;
  159. setConfig(cfg);
  160. }
  161. if (config.printCfg) {
  162. console.log(JSON.stringify({ ...ServerConfig, ...LimitsConfig }, null, 2));
  163. process.exit(0);
  164. }
  165. validateServerConfig();
  166. } catch (e) {
  167. console.error('' + e);
  168. process.exit(1);
  169. }
  170. }
  171. export function configureLocal() {
  172. const parser = new argparse.ArgumentParser({
  173. // version: VOLUME_SERVER_VERSION,
  174. add_help: true,
  175. description: VOLUME_SERVER_HEADER
  176. });
  177. parser.add_argument('--jobs', { help: `Path to a JSON file with job specification.`, required: false });
  178. parser.add_argument('--jobsTemplate', { help: 'Print example template for jobs.json and exit.', required: false, action: 'store_true' });
  179. addJsonConfigArgs(parser);
  180. addLimitsArgs(parser);
  181. const config = parser.parse_args() as LimitsConfig & ServerJsonConfig;
  182. if (config.cfgTemplate) {
  183. console.log(JSON.stringify(DefaultLimitsConfig, null, 2));
  184. process.exit(0);
  185. }
  186. try {
  187. setLimitsConfig(config); // sets the config for global use
  188. if (config.cfg) {
  189. const cfg = JSON.parse(fs.readFileSync(config.cfg, 'utf8')) as FullServerConfig;
  190. setLimitsConfig(cfg);
  191. }
  192. if (config.printCfg) {
  193. console.log(JSON.stringify(LimitsConfig, null, 2));
  194. process.exit(0);
  195. }
  196. return config as LimitsConfig & { jobs: string, jobsTemplate: any };
  197. } catch (e) {
  198. console.error('' + e);
  199. process.exit(1);
  200. }
  201. }