<commit_before>/**
 * @license
 * Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
 * This code may only be used under the BSD style license found at
 * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
 * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
 * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
 * Google as part of the polymer project is also subject to an additional IP
 * rights grant found at http://polymer.github.io/PATENTS.txt
 */

import * as http from 'http';
import * as net from 'net';
import * as querystring from 'querystring';

import Koa = require('koa');
import mount = require('koa-mount');
import serve = require('koa-static');
import bodyParser = require('koa-bodyparser');
import {UAParser} from 'ua-parser-js';

import {BenchmarkResponse, Deferred, BenchmarkSpec, BenchmarkResult, PendingBenchmark} from './types';

export class Server {
  readonly url: string;
  private readonly server: net.Server;

  // Even though we're running benchmarks in series, we give each run an id and
  // make sure that we associate result messages with the correct run. This
  // prevents any spurrious race conditions and enables one runner to launch
  // multiple clients eventually.
  private currentRunId = 0;
  private currentRunBytes = 0;
  private readonly pendingRuns = new Map<string, PendingBenchmark>();
  private resultSubmitted = new Deferred<BenchmarkResult>();

  static start(opts: {host: string, port: number, rootDir: string}):
      Promise<Server> {
    return new Promise((resolve) => {
      const server = http.createServer();
      server.listen(
          {host: opts.host, port: opts.port},
          () => resolve(new Server(server, opts.rootDir)));
    });
  }

  constructor(server: http.Server, rootDir: string) {
    this.server = server;

    const app = new Koa();
    app.use(bodyParser());
    app.use(mount('/submitResults', this.submitResults.bind(this)));
    app.use(this.rewriteVersionUrls.bind(this));
    app.use(this.recordBytesSent.bind(this));
    app.use(mount('/', serve(rootDir, {index: 'index.html'})));
    this.server.on('request', app.callback());

    const address = (this.server.address() as net.AddressInfo);
    let host = address.address;
    if (address.family === 'IPv6') {
      host = `[${host}]`;
    }
    this.url = `http://${host}:${address.port}`;
  }

  runBenchmark(spec: BenchmarkSpec):
      {url: string, result: Promise<BenchmarkResult>} {
    const id = (this.currentRunId++).toString();
    const run: PendingBenchmark = {
      id,
      spec,
      deferred: new Deferred<BenchmarkResult>()
    };
    this.pendingRuns.set(id, run);
    return {
      url: this.specUrl(spec, id),
      result: run.deferred.promise,
    };
  }

  specUrl(spec: BenchmarkSpec, id?: string): string {
    const params: {
      runId?: string,
      variant?: string,
      config?: string,
    } = {};
    if (id !== undefined) {
      params.runId = id;
    }
    if (spec.variant !== undefined) {
      params.variant = spec.variant;
    }
    if (spec.config !== undefined) {
      params.config = JSON.stringify(spec.config);
    }
    return `${this.url}/benchmarks/${spec.implementation}/` +
        (spec.version.label === 'default' ? '' :
                                            `versions/${spec.version.label}/`) +
        `${spec.name}/?${querystring.stringify(params)}`;
  }

  async * streamResults(): AsyncIterableIterator<BenchmarkResult> {
    while (true) {
      yield await this.resultSubmitted.promise;
    }
  }

  async close() {
    return new Promise((resolve, reject) => {
      this.server.close((error: unknown) => {
        if (error) {
          reject(error);
        } else {
          resolve();
        }
      });
    });
  }

  private async recordBytesSent(ctx: Koa.Context, next: () => Promise<void>):
      Promise<void> {<commit_msg>Add comment about assumption of serial runs<commit_after>/**
 * @license
 * Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
 * This code may only be used under the BSD style license found at
 * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
 * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
 * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
 * Google as part of the polymer project is also subject to an additional IP
 * rights grant found at http://polymer.github.io/PATENTS.txt
 */

import * as http from 'http';
import * as net from 'net';
import * as querystring from 'querystring';

import Koa = require('koa');
import mount = require('koa-mount');
import serve = require('koa-static');
import bodyParser = require('koa-bodyparser');
import {UAParser} from 'ua-parser-js';

import {BenchmarkResponse, Deferred, BenchmarkSpec, BenchmarkResult, PendingBenchmark} from './types';

export class Server {
  readonly url: string;
  private readonly server: net.Server;

  // Even though we're running benchmarks in series, we give each run an id and
  // make sure that we associate result messages with the correct run. This
  // prevents any spurrious race conditions and enables one runner to launch
  // multiple clients eventually.
  private currentRunId = 0;
  private currentRunBytes = 0;
  private readonly pendingRuns = new Map<string, PendingBenchmark>();
  private resultSubmitted = new Deferred<BenchmarkResult>();

  static start(opts: {host: string, port: number, rootDir: string}):
      Promise<Server> {
    return new Promise((resolve) => {
      const server = http.createServer();
      server.listen(
          {host: opts.host, port: opts.port},
          () => resolve(new Server(server, opts.rootDir)));
    });
  }

  constructor(server: http.Server, rootDir: string) {
    this.server = server;

    const app = new Koa();
    app.use(bodyParser());
    app.use(mount('/submitResults', this.submitResults.bind(this)));
    app.use(this.rewriteVersionUrls.bind(this));
    app.use(this.recordBytesSent.bind(this));
    app.use(mount('/', serve(rootDir, {index: 'index.html'})));
    this.server.on('request', app.callback());

    const address = (this.server.address() as net.AddressInfo);
    let host = address.address;
    if (address.family === 'IPv6') {
      host = `[${host}]`;
    }
    this.url = `http://${host}:${address.port}`;
  }

  runBenchmark(spec: BenchmarkSpec):
      {url: string, result: Promise<BenchmarkResult>} {
    const id = (this.currentRunId++).toString();
    const run: PendingBenchmark = {
      id,
      spec,
      deferred: new Deferred<BenchmarkResult>()
    };
    this.pendingRuns.set(id, run);
    return {
      url: this.specUrl(spec, id),
      result: run.deferred.promise,
    };
  }

  specUrl(spec: BenchmarkSpec, id?: string): string {
    const params: {
      runId?: string,
      variant?: string,
      config?: string,
    } = {};
    if (id !== undefined) {
      params.runId = id;
    }
    if (spec.variant !== undefined) {
      params.variant = spec.variant;
    }
    if (spec.config !== undefined) {
      params.config = JSON.stringify(spec.config);
    }
    return `${this.url}/benchmarks/${spec.implementation}/` +
        (spec.version.label === 'default' ? '' :
                                            `versions/${spec.version.label}/`) +
        `${spec.name}/?${querystring.stringify(params)}`;
  }

  async * streamResults(): AsyncIterableIterator<BenchmarkResult> {
    while (true) {
      yield await this.resultSubmitted.promise;
    }
  }

  async close() {
    return new Promise((resolve, reject) => {
      this.server.close((error: unknown) => {
        if (error) {
          reject(error);
        } else {
          resolve();
        }
      });
    });
  }

  private async recordBytesSent(ctx: Koa.Context, next: () => Promise<void>):
      Promise<void> {
    // Note this assumes serial runs, as we guarantee in automatic mode. If we
    // ever wanted to support parallel requests, we would require some kind of
    // session tracking.
{'fraction_non_alphanumeric': 0.13850893407270487, 'fraction_numerical': 0.001725200246457178, 'mean_word_length': 3.039820806371329, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7180388', 'n_tokens_mistral': 2435, 'n_tokens_neox': 2364, 'n_words': 671}