<commit_before>const os = require('os');
const fork = require('child_process').fork;
const autocannon = require('autocannon');
const serie = require('fastseries');

const SERVERS = [
	// 'node-static',
	// 'serve-files',
	// 'serve-files-fs-cache',
	'serve-files-fs-cache-v2',
	// 'serve-static',
	// 'st',
	// 'statique'
];

const DEFAULT_DURATION = 60;
const DEFAULT_CONNECTIONS = 100;

const DURATION = parseInt(process.env.DURATION || DEFAULT_DURATION, 10);
const CONNECTIONS = parseInt(process.env.CONNECTIONS || DEFAULT_CONNECTIONS, 10);

const MINUTE_AS_SECONDS = 60;

var docker = (function checkDocker () {
	const fs = require('fs'); // eslint-disable-line global-require
	try {
		return fs.readFileSync('/proc/self/cgroup', 'utf8').indexOf('/docker/') !== -1
			&& (fs.readFileSync('/etc/os-release', 'utf8').match(/PRETTY_NAME="([^"]+)"/) || [null, 'unknown'])[1];
	}
	catch (e) {
		return false;
	}
})();
var where = docker ? `inside Docker (${docker})` : `natively (${os.release()})`;
console.log(`Running ${where} with Node ${process.version} and ${os.cpus()[0].model} x ${os.cpus().length}.`);
console.log(`Testing ${SERVERS.length} servers, with ${DURATION} seconds of ${CONNECTIONS} simultaneous connections each.`);
console.log(`Test will take approximately ${(DURATION * SERVERS.length) / MINUTE_AS_SECONDS} minute(s).`);

/**
 * @private
 * @param {string}   name
 * @param {Function} callback
 */
function run (name, callback) {
	var s = fork(`${__dirname}/${name}.js`);

	s.once('message', m => {
		if (!m.port) {
			s.kill();
			s = null;
			callback(new Error('No `ready` message received from server script'));
			return;
		}

		process.stdout.write(`. ${name}`);
		autocannon({
			url        : `http://localhost:${m.port}/index.js?foo[bar]=baz`,
			title      : name,
			duration   : DURATION,
			connections: CONNECTIONS
		}, (err, result) => {
			s.kill();
			s = null;
			process.stdout.cursorTo(0);
			process.stdout.write(`${err || result.non2xx ? '✗' : '✔'} ${name}\n`);
			callback(err, result);
		});
	});
}

serie({results: true})({}, run, SERVERS, (err, results) => {
	if (err) {
		console.error(err);
		return;
	}

	var tableData = results.map(Result);
	tableData.sort((a, b) => {
		if (a.non2xx !== b.non2xx) {
			return a.non2xx - b.non2xx;
		}

		if (a.requests !== b.requests) {
			return b.requests - a.requests;
		}

		if (a.latency !== b.latency) {
			return a.latency - b.latency;
		}

		return 0;
	});

	console.table(tableData);
});

/**
 * @private
 * @class
 * @param {object} data
 * @param {string} data.title
 * @param {string} data.url
 * @param {object} data.requests
 * @param {number} data.requests.average
 * @param {number} data.requests.mean
 * @param {number} data.requests.stddev
 * @param {number} data.requests.min
 * @param {number} data.requests.max
 * @param {number} data.requests.total
 * @param {number} data.requests.p0_001
 * @param {number} data.requests.p0_01
 * @param {number} data.requests.p0_1
 * @param {number} data.requests.p1
 * @param {number} data.requests.p2_5
 * @param {number} data.requests.p10
 * @param {number} data.requests.p25
 * @param {number} data.requests.p50
 * @param {number} data.requests.p75
 * @param {number} data.requests.p90
 * @param {number} data.requests.p97_5
 * @param {number} data.requests.p99
 * @param {number} data.requests.p99_9
 * @param {number} data.requests.p99_99
 * @param {number} data.requests.p99_999
 * @param {number} data.requests.sent
 * @param {object} data.latency
 * @param {number} data.latency.average
 * @param {number} data.latency.mean
 * @param {number} data.latency.stddev
 * @param {number} data.latency.min
 * @param {number} data.latency.max
 * @param {number} data.latency.p0_001
 * @param {number} data.latency.p0_01
 * @param {number} data.latency.p0_1
 * @param {number} data.latency.p1
 * @param {number} data.latency.p2_5
 * @param {number} data.latency.p10
 * @param {number} data.latency.p25
 * @param {number} data.latency.p50
 * @param {number} data.latency.p75
 * @param {number} data.latency.p90
 * @param {number} data.latency.p97_5
 * @param {number} data.latency.p99
 * @param {number} data.latency.p99_9
 * @param {number} data.latency.p99_99
 * @param {number} data.latency.p99_999
 * @param {object} data.throughput
 * @param {number} data.throughput.average
 * @param {number} data.throughput.mean
 * @param {number} data.throughput.stddev
 * @param {number} data.throughput.min
 * @param {number} data.throughput.max
 * @param {number} data.throughput.total
 * @param {number} data.throughput.p0_001
 * @param {number} data.throughput.p0_01
 * @param {number} data.throughput.p0_1
 * @param {number} data.throughput.p1
 * @param {number} data.throughput.p2_5
 * @param {number} data.throughput.p10
 * @param {number} data.throughput.p25
 * @param {number} data.throughput.p50
 * @param {number} data.throughput.p75
 * @param {number} data.throughput.p90
 * @param {number} data.throughput.p97_5
 * @param {number} data.throughput.p99
 * @param {number} data.throughput.p99_9
 * @param {number} data.throughput.p99_99
 * @param {number} data.throughput.p99_999
 * @param {number} data.errors
 * @param {number} data.timeouts
 * @param {number} data.duration
 * @param {string} data.start
 * @param {string} data.finish
 * @param {number} data.connections
 * @param {number} data.pipelining
 * @param {number} data.non2xx
 * @param {number} data.1xx
 * @param {number} data.2xx
 * @param {number} data.3xx
 * @param {number} data.4xx
 * @param {number} data.5xx
 */
function Result (data) {
	if (!(this instanceof Result)) {
		return new Result(data);
	}

	this.title = data.title;
	this.requests = data.requests.p99_9;
	this.latency = data.latency.p99_9;
	this.bytes = data.throughput.p99_9;
	this.timeouts = data.timeouts;
	this.errors = data.errors;
	this.non2xx = data.non2xx;
}
<commit_msg>fix: benchmark output to non-tty stream<commit_after>const os = require('os');
const fork = require('child_process').fork;
const autocannon = require('autocannon');
const serie = require('fastseries');

const SERVERS = [
	// 'node-static',
	// 'serve-files',
	// 'serve-files-fs-cache',
	'serve-files-fs-cache-v2',
	// 'serve-static',
	// 'st',
	// 'statique'
];

const DEFAULT_DURATION = 60;
const DEFAULT_CONNECTIONS = 100;

const DURATION = parseInt(process.env.DURATION || DEFAULT_DURATION, 10);
const CONNECTIONS = parseInt(process.env.CONNECTIONS || DEFAULT_CONNECTIONS, 10);

const MINUTE_AS_SECONDS = 60;
const IS_TTY = process.stdout.isTTY;

var docker = (function checkDocker () {
	const fs = require('fs'); // eslint-disable-line global-require
	try {
		return fs.readFileSync('/proc/self/cgroup', 'utf8').indexOf('/docker/') !== -1
			&& (fs.readFileSync('/etc/os-release', 'utf8').match(/PRETTY_NAME="([^"]+)"/) || [null, 'unknown'])[1];
	}
	catch (e) {
		return false;
	}
})();
var where = docker ? `inside Docker (${docker})` : `natively (${os.release()})`;
console.log(`Running ${where} with Node ${process.version} and ${os.cpus()[0].model} x ${os.cpus().length}.`);
console.log(`Testing ${SERVERS.length} servers, with ${DURATION} seconds of ${CONNECTIONS} simultaneous connections each.`);
if (IS_TTY) {
	console.log(`Test will take approximately ${(DURATION * SERVERS.length) / MINUTE_AS_SECONDS} minute(s).`);
}

/**
 * @private
 * @param {string}   name
 * @param {Function} callback
 */
function run (name, callback) {
	var s = fork(`${__dirname}/${name}.js`);

	s.once('message', m => {
		if (!m.port) {
			s.kill();
			s = null;
			callback(new Error('No `ready` message received from server script'));
			return;
		}

		if (IS_TTY) {
			process.stdout.write(`. ${name}`);
		}
		autocannon({
			url        : `http://localhost:${m.port}/index.js?foo[bar]=baz`,
			title      : name,
			duration   : DURATION,
			connections: CONNECTIONS
		}, (err, result) => {
			s.kill();
			s = null;
			if (IS_TTY) {
				process.stdout.cursorTo(0);
				process.stdout.write(`${err || result.non2xx ? '✗' : '✔'} ${name}\n`);
			}
			callback(err, result);
		});
	});
}

serie({results: true})({}, run, SERVERS, (err, results) => {
	if (err) {
		console.error(err);
		return;
	}

	var tableData = results.map(Result);
	tableData.sort((a, b) => {
		if (a.non2xx !== b.non2xx) {
			return a.non2xx - b.non2xx;
		}

		if (a.requests !== b.requests) {
			return b.requests - a.requests;
		}

		if (a.latency !== b.latency) {
			return a.latency - b.latency;
		}

		return 0;
	});

	console.table(tableData);
});

/**
 * @private
 * @class
 * @param {object} data
 * @param {string} data.title
 * @param {string} data.url
 * @param {object} data.requests
 * @param {number} data.requests.average
 * @param {number} data.requests.mean
 * @param {number} data.requests.stddev
 * @param {number} data.requests.min
 * @param {number} data.requests.max
 * @param {number} data.requests.total
 * @param {number} data.requests.p0_001
 * @param {number} data.requests.p0_01
 * @param {number} data.requests.p0_1
 * @param {number} data.requests.p1
 * @param {number} data.requests.p2_5
 * @param {number} data.requests.p10
 * @param {number} data.requests.p25
 * @param {number} data.requests.p50
 * @param {number} data.requests.p75
 * @param {number} data.requests.p90
 * @param {number} data.requests.p97_5
 * @param {number} data.requests.p99
 * @param {number} data.requests.p99_9
 * @param {number} data.requests.p99_99
 * @param {number} data.requests.p99_999
 * @param {number} data.requests.sent
 * @param {object} data.latency
 * @param {number} data.latency.average
 * @param {number} data.latency.mean
 * @param {number} data.latency.stddev
 * @param {number} data.latency.min
 * @param {number} data.latency.max
 * @param {number} data.latency.p0_001
 * @param {number} data.latency.p0_01
 * @param {number} data.latency.p0_1
 * @param {number} data.latency.p1
 * @param {number} data.latency.p2_5
 * @param {number} data.latency.p10
 * @param {number} data.latency.p25
 * @param {number} data.latency.p50
 * @param {number} data.latency.p75
 * @param {number} data.latency.p90
 * @param {number} data.latency.p97_5
 * @param {number} data.latency.p99
 * @param {number} data.latency.p99_9
 * @param {number} data.latency.p99_99
 * @param {number} data.latency.p99_999
 * @param {object} data.throughput
 * @param {number} data.throughput.average
 * @param {number} data.throughput.mean
 * @param {number} data.throughput.stddev
 * @param {number} data.throughput.min
 * @param {number} data.throughput.max
 * @param {number} data.throughput.total
 * @param {number} data.throughput.p0_001
 * @param {number} data.throughput.p0_01
 * @param {number} data.throughput.p0_1
 * @param {number} data.throughput.p1
 * @param {number} data.throughput.p2_5
 * @param {number} data.throughput.p10
 * @param {number} data.throughput.p25
 * @param {number} data.throughput.p50
 * @param {number} data.throughput.p75
 * @param {number} data.throughput.p90
 * @param {number} data.throughput.p97_5
 * @param {number} data.throughput.p99
 * @param {number} data.throughput.p99_9
 * @param {number} data.throughput.p99_99
 * @param {number} data.throughput.p99_999
 * @param {number} data.errors
 * @param {number} data.timeouts
 * @param {number} data.duration
 * @param {string} data.start
 * @param {string} data.finish
 * @param {number} data.connections
 * @param {number} data.pipelining
 * @param {number} data.non2xx
 * @param {number} data.1xx
 * @param {number} data.2xx
 * @param {number} data.3xx
 * @param {number} data.4xx
 * @param {number} data.5xx
 */
function Result (data) {
	if (!(this instanceof Result)) {
		return new Result(data);
	}

	this.title = data.title;
	this.requests = data.requests.p99_9;
	this.latency = data.latency.p99_9;
	this.bytes = data.throughput.p99_9;
	this.timeouts = data.timeouts;
	this.errors = data.errors;
	this.non2xx = data.non2xx;
}
<|endoftext|>


<commit_before>/**
 * this plugin adds the RxCollection.syncGraphQl()-function to rxdb
 * you can use it to sync collections with remote graphql endpoint
 */

import {
    BehaviorSubject,
    Subject
} from 'rxjs';
import {
    first,
    filter
} from 'rxjs/operators';
import GraphQLClient from 'graphql-client';

import {
    promiseWait
} from '../../util';

import Core from '../../core';
import {
    hash,
    clone
} from '../../util';

import {
    DEFAULT_MODIFIER,
    wasRevisionfromPullReplication,
    createRevisionForPulledDocument,
    getDocsWithRevisionsFromPouch
} from './helper';
import {
    setLastPushSequence,
    getLastPullDocument,
    setLastPullDocument,
    getChangesSinceLastPushSequence
} from './crawling-checkpoint';

import RxDBWatchForChangesPlugin from '../watch-for-changes';
import {
    changeEventfromPouchChange
} from '../../rx-change-event';

/**
 * add the watch-for-changes-plugin
 * so pouchdb will emit events when something gets written to it
 */
Core.plugin(RxDBWatchForChangesPlugin);

export class RxGraphQLReplicationState {
    constructor(
        collection,
        url,
        headers,
        pull,
        push,
        deletedFlag,
        live,
        liveInterval,
        retryTime
    ) {
        this.collection = collection;
        this.client = GraphQLClient({
            url,
            headers
        });
        this.endpointHash = hash(url);

        this.pull = pull;
        this.push = push;

        this.deletedFlag = deletedFlag;
        this.live = live;
        this.liveInterval = liveInterval;
        this.retryTime = retryTime;

        this._runQueueCount = 0;
        this._subs = [];
        this._runningPromise = Promise.resolve();
        this._subjects = {
            recieved: new Subject(), // all documents that are recieved from the endpoint
            send: new Subject(), // all documents that are send to the endpoint
            error: new Subject(), // all errors that are revieced from the endpoint, emits new Error() objects
            canceled: new BehaviorSubject(false), // true when the replication was canceled
            active: new BehaviorSubject(false), // true when something is running, false when not
            initialReplicationComplete: new BehaviorSubject(false) // true the initial replication-cycle is over
        };

        this._prepare();
    }

    /**
     * things that are more complex to not belong into the constructor
     */
    _prepare() {
        // stop sync when collection gets destroyed
        this.collection.onDestroy.then(() => {
            this.cancel();
        });

        // create getters for the observables
        Object.keys(this._subjects).forEach(key => {
            Object.defineProperty(this, key + '$', {
                get: function () {
                    return this._subjects[key].asObservable();
                }
            });
        });
    }

    isStopped() {
        if (!this.live && this._subjects.initialReplicationComplete._value) return true;
        if (this._subjects.canceled._value) return true;
        else return false;
    }

    awaitInitialReplication() {
        return this.initialReplicationComplete$.pipe(
            filter(v => v === true),
            first()
        ).toPromise();
    }

    // ensures this._run() does not run in parallel
    async run() {
        if (this.isStopped()) {
            return;
        }

        if (this._runQueueCount > 2) {
            return this._runningPromise;
        }

        this._runQueueCount++;
        this._runningPromise = this._runningPromise.then(async () => {
            this._subjects.active.next(true);
            const willRetry = await this._run();
            this._subjects.active.next(false);

            if (!willRetry && this._subjects.initialReplicationComplete._value === false)
                this._subjects.initialReplicationComplete.next(true);

            this._runQueueCount--;
        });
        return this._runningPromise;
    }

    async _run() {
        let willRetry = false;

        if (this.push) {
            const ok = await this.runPush();
            if (!ok) {
                willRetry = true;
                setTimeout(() => this.run(), this.retryTime);
            }
        }

        if (this.pull) {
            const ok = await this.runPull();
            if (!ok) {
                willRetry = true;
                setTimeout(() => this.run(), this.retryTime);
            }
        }

        return willRetry;

    }

    /**
     * @return {boolean} true if no errors occured
     */
    async runPull() {
        // console.log('RxGraphQLReplicationState.runPull(): start');
        if (this.isStopped()) return;

        const latestDocument = await getLastPullDocument(this.collection, this.endpointHash);
        const latestDocumentData = latestDocument ? latestDocument : null;
        const pullGraphQL = this.pull.queryBuilder(latestDocumentData);

        let result;
        try {
            result = await this.client.query(pullGraphQL.query, pullGraphQL.variables);
            if (result.errors) {
                throw new Error(result.errors);
            }
        } catch (err) {
            this._subjects.error.next(err);
            setTimeout(() => this.run(), this.retryTime);
            return false;
        }

        // this assumes that there will be always only one property in the response
        // is this correct?
        const data = result.data[Object.keys(result.data)[0]];

        const modified = data.map(doc => this.pull.modifier(doc));

        const docIds = modified.map(doc => doc[this.collection.schema.primaryPath]);
        const docsWithRevisions = await getDocsWithRevisionsFromPouch(
            this.collection,
            docIds
        );
        await Promise.all(modified.map(doc => this.handleDocumentFromRemote(doc, docsWithRevisions)));
        modified.map(doc => this._subjects.recieved.next(doc));

        if (modified.length === 0) {
            if (this.live) {
                // console.log('no more docs, wait for ping');
            } else {
                // console.log('RxGraphQLReplicationState._run(): no more docs and not live; complete = true');
            }
        } else {
            const newLatestDocument = modified[modified.length - 1];
            await setLastPullDocument(
                this.collection,
                this.endpointHash,
                newLatestDocument
            );

            // we have more docs, re-run
            await this.runPull();
        }

        return true;
    }

    async runPush() {
        // console.log('RxGraphQLReplicationState.runPush(): start');

        const changes = await getChangesSinceLastPushSequence(
            this.collection,
            this.endpointHash,
            this.push.batchSize
        );

        const changesWithDocs = changes.results.map(change => {
            let doc = change.doc;

            doc[this.deletedFlag] = !!change.deleted;
            delete doc._rev;
            delete doc._deleted;
            delete doc._attachments;

            doc = this.push.modifier(doc);

            const seq = change.seq;
            return {
                doc,
                seq
            };
        });

        let lastSuccessfullChange = null;
        try {
            /**
             * we cannot run all queries parallel
             * because then we would not know
             * where to start again on errors
             * so we run through the docs in series
             */
            for (let i = 0; i < changesWithDocs.length; i++) {
                const changeWithDoc = changesWithDocs[i];
                const pushObj = this.push.queryBuilder(changeWithDoc.doc);
                const result = await this.client.query(pushObj.query, pushObj.variables);
                if (result.errors) {
                    throw new Error(result.errors);
                } else {
                    this._subjects.send.next(changeWithDoc.doc);
                    lastSuccessfullChange = changeWithDoc;
                }
            }
        } catch (err) {

            if (lastSuccessfullChange) {
                await setLastPushSequence(
                    this.collection,
                    this.endpointHash,
                    lastSuccessfullChange.seq
                );
            }

            this._subjects.error.next(err);
            setTimeout(() => this.run(), this.retryTime);
            return false;
        }

        // all docs where successfull, so we use the seq of the changes-fetch
        await setLastPushSequence(
            this.collection,
            this.endpointHash,
            changes.last_seq
        );

        if (changes.results.length === 0) {
            if (this.live) {
                // console.log('no more docs to push, wait for ping');
            } else {
                // console.log('RxGraphQLReplicationState._runPull(): no more docs to push and not live; complete = true');
            }
        } else {
            // we have more docs, re-run
            await this.runPush();
        }

        return true;
    }

    async handleDocumentFromRemote(doc, docsWithRevisions) {
        const deletedValue = doc[this.deletedFlag];
        const toPouch = this.collection._handleToPouch(doc);
        // console.log('handleDocumentFromRemote(' + toPouch._id + ') start');
        toPouch._deleted = deletedValue;
        delete toPouch[this.deletedFlag];
        const primaryValue = toPouch._id;

        const pouchState = docsWithRevisions[primaryValue];
        let newRevision = createRevisionForPulledDocument(
            this.endpointHash,
            toPouch
        );
        if (pouchState) {
            const newRevisionHeight = pouchState.revisions.start + 1;
            const revisionId = newRevision;
            newRevision = newRevisionHeight + '-' + newRevision;
            toPouch._revisions = {
                start: newRevisionHeight,
                ids: pouchState.revisions.ids
            };
            toPouch._revisions.ids.unshift(revisionId);
        } else {
            newRevision = '1-' + newRevision;
        }
        toPouch._rev = newRevision;

        await this.collection.pouch.bulkDocs(
            [
                toPouch
            ], {
                new_edits: false
            }
        );

        /**
         * because bulkDocs with new_edits: false
         * does not stream changes to the pouchdb,
         * we create the event and emit it,
         * so other instances get informed about it
         */
        const originalDoc = clone(toPouch);
        originalDoc._deleted = deletedValue;
        delete originalDoc[this.deletedFlag];
        originalDoc._rev = newRevision;
        const cE = changeEventfromPouchChange(
            originalDoc,
            this.collection
        );
        this.collection.$emit(cE);
    }

    cancel() {
        if (this.isStopped()) return;

        if (this.changesSub) this.changesSub.cancel();

        this._subjects.canceled.next(true);
        // TODO
    }
}

export function syncGraphQL({
    url,
    headers = {},
    waitForLeadership = true,
    pull,
    push,
    deletedFlag,
    live = false,
    liveInterval = 1000 * 10, // in ms
    retryTime = 1000 * 5, // in ms
    autoStart = true // if this is false, the replication does nothing at start
}) {
    console.log('syncGraphQL(): 1');
    const collection = this;

    // fill in defaults for pull & push
    if (pull) {
        if (!pull.modifier) pull.modifier = DEFAULT_MODIFIER;
    }
    if (push) {
        if (!push.modifier) push.modifier = DEFAULT_MODIFIER;
    }

    // ensure the collection is listening to plain-pouchdb writes
    collection.watchForChanges();

    const replicationState = new RxGraphQLReplicationState(
        collection,
        url,
        headers,
        pull,
        push,
        deletedFlag,
        live,
        liveInterval,
        retryTime
    );

    if (!autoStart) return replicationState;

    // run internal so .sync() does not have to be async
    const waitTillRun = waitForLeadership ? this.database.waitForLeadership() : promiseWait(0);
    waitTillRun.then(() => {

        // trigger run once
        replicationState.run();

        // start sync-interval
        if (replicationState.live) {

            if (pull) {
                (async () => {
                    while (!replicationState.isStopped()) {
                        await promiseWait(replicationState.liveInterval);
                        if (replicationState.isStopped()) return;
                        await replicationState.run();
                    }
                })();
            }

            if (push) {
                replicationState.changesSub = collection.pouch.changes({
                    since: 'now',
                    live: true,
                    include_docs: true
                }).on('change', function (change) {
                    if (replicationState.isStopped()) return;

                    const rev = change.doc._rev;
                    if (!wasRevisionfromPullReplication(
                        replicationState.endpointHash,
                        rev
                    )) {
                        replicationState.run();
                    }
                });

            }
        }
    });

    return replicationState;
}

export const rxdb = true;
export const prototypes = {
    RxCollection: proto => {
        proto.syncGraphQL = syncGraphQL;
    }
};

export default {
    rxdb,
    prototypes
};<commit_msg>REMOVE log<commit_after>/**
 * this plugin adds the RxCollection.syncGraphQl()-function to rxdb
 * you can use it to sync collections with remote graphql endpoint
 */

import {
    BehaviorSubject,
    Subject
} from 'rxjs';
import {
    first,
    filter
} from 'rxjs/operators';
import GraphQLClient from 'graphql-client';

import {
    promiseWait
} from '../../util';

import Core from '../../core';
import {
    hash,
    clone
} from '../../util';

import {
    DEFAULT_MODIFIER,
    wasRevisionfromPullReplication,
    createRevisionForPulledDocument,
    getDocsWithRevisionsFromPouch
} from './helper';
import {
    setLastPushSequence,
    getLastPullDocument,
    setLastPullDocument,
    getChangesSinceLastPushSequence
} from './crawling-checkpoint';

import RxDBWatchForChangesPlugin from '../watch-for-changes';
import {
    changeEventfromPouchChange
} from '../../rx-change-event';

/**
 * add the watch-for-changes-plugin
 * so pouchdb will emit events when something gets written to it
 */
Core.plugin(RxDBWatchForChangesPlugin);

export class RxGraphQLReplicationState {
    constructor(
        collection,
        url,
        headers,
        pull,
        push,
        deletedFlag,
        live,
        liveInterval,
        retryTime
    ) {
        this.collection = collection;
        this.client = GraphQLClient({
            url,
            headers
        });
        this.endpointHash = hash(url);

        this.pull = pull;
        this.push = push;

        this.deletedFlag = deletedFlag;
        this.live = live;
        this.liveInterval = liveInterval;
        this.retryTime = retryTime;

        this._runQueueCount = 0;
        this._subs = [];
        this._runningPromise = Promise.resolve();
        this._subjects = {
            recieved: new Subject(), // all documents that are recieved from the endpoint
            send: new Subject(), // all documents that are send to the endpoint
            error: new Subject(), // all errors that are revieced from the endpoint, emits new Error() objects
            canceled: new BehaviorSubject(false), // true when the replication was canceled
            active: new BehaviorSubject(false), // true when something is running, false when not
            initialReplicationComplete: new BehaviorSubject(false) // true the initial replication-cycle is over
        };

        this._prepare();
    }

    /**
     * things that are more complex to not belong into the constructor
     */
    _prepare() {
        // stop sync when collection gets destroyed
        this.collection.onDestroy.then(() => {
            this.cancel();
        });

        // create getters for the observables
        Object.keys(this._subjects).forEach(key => {
            Object.defineProperty(this, key + '$', {
                get: function () {
                    return this._subjects[key].asObservable();
                }
            });
        });
    }

    isStopped() {
        if (!this.live && this._subjects.initialReplicationComplete._value) return true;
        if (this._subjects.canceled._value) return true;
        else return false;
    }

    awaitInitialReplication() {
        return this.initialReplicationComplete$.pipe(
            filter(v => v === true),
            first()
        ).toPromise();
    }

    // ensures this._run() does not run in parallel
    async run() {
        if (this.isStopped()) {
            return;
        }

        if (this._runQueueCount > 2) {
            return this._runningPromise;
        }

        this._runQueueCount++;
        this._runningPromise = this._runningPromise.then(async () => {
            this._subjects.active.next(true);
            const willRetry = await this._run();
            this._subjects.active.next(false);

            if (!willRetry && this._subjects.initialReplicationComplete._value === false)
                this._subjects.initialReplicationComplete.next(true);

            this._runQueueCount--;
        });
        return this._runningPromise;
    }

    async _run() {
        let willRetry = false;

        if (this.push) {
            const ok = await this.runPush();
            if (!ok) {
                willRetry = true;
                setTimeout(() => this.run(), this.retryTime);
            }
        }

        if (this.pull) {
            const ok = await this.runPull();
            if (!ok) {
                willRetry = true;
                setTimeout(() => this.run(), this.retryTime);
            }
        }

        return willRetry;

    }

    /**
     * @return {boolean} true if no errors occured
     */
    async runPull() {
        // console.log('RxGraphQLReplicationState.runPull(): start');
        if (this.isStopped()) return;

        const latestDocument = await getLastPullDocument(this.collection, this.endpointHash);
        const latestDocumentData = latestDocument ? latestDocument : null;
        const pullGraphQL = this.pull.queryBuilder(latestDocumentData);

        let result;
        try {
            result = await this.client.query(pullGraphQL.query, pullGraphQL.variables);
            if (result.errors) {
                throw new Error(result.errors);
            }
        } catch (err) {
            this._subjects.error.next(err);
            setTimeout(() => this.run(), this.retryTime);
            return false;
        }

        // this assumes that there will be always only one property in the response
        // is this correct?
        const data = result.data[Object.keys(result.data)[0]];

        const modified = data.map(doc => this.pull.modifier(doc));

        const docIds = modified.map(doc => doc[this.collection.schema.primaryPath]);
        const docsWithRevisions = await getDocsWithRevisionsFromPouch(
            this.collection,
            docIds
        );
        await Promise.all(modified.map(doc => this.handleDocumentFromRemote(doc, docsWithRevisions)));
        modified.map(doc => this._subjects.recieved.next(doc));

        if (modified.length === 0) {
            if (this.live) {
                // console.log('no more docs, wait for ping');
            } else {
                // console.log('RxGraphQLReplicationState._run(): no more docs and not live; complete = true');
            }
        } else {
            const newLatestDocument = modified[modified.length - 1];
            await setLastPullDocument(
                this.collection,
                this.endpointHash,
                newLatestDocument
            );

            // we have more docs, re-run
            await this.runPull();
        }

        return true;
    }

    async runPush() {
        // console.log('RxGraphQLReplicationState.runPush(): start');

        const changes = await getChangesSinceLastPushSequence(
            this.collection,
            this.endpointHash,
            this.push.batchSize
        );

        const changesWithDocs = changes.results.map(change => {
            let doc = change.doc;

            doc[this.deletedFlag] = !!change.deleted;
            delete doc._rev;
            delete doc._deleted;
            delete doc._attachments;

            doc = this.push.modifier(doc);

            const seq = change.seq;
            return {
                doc,
                seq
            };
        });

        let lastSuccessfullChange = null;
        try {
            /**
             * we cannot run all queries parallel
             * because then we would not know
             * where to start again on errors
             * so we run through the docs in series
             */
            for (let i = 0; i < changesWithDocs.length; i++) {
                const changeWithDoc = changesWithDocs[i];
                const pushObj = this.push.queryBuilder(changeWithDoc.doc);
                const result = await this.client.query(pushObj.query, pushObj.variables);
                if (result.errors) {
                    throw new Error(result.errors);
                } else {
                    this._subjects.send.next(changeWithDoc.doc);
                    lastSuccessfullChange = changeWithDoc;
                }
            }
        } catch (err) {

            if (lastSuccessfullChange) {
                await setLastPushSequence(
                    this.collection,
                    this.endpointHash,
                    lastSuccessfullChange.seq
                );
            }

            this._subjects.error.next(err);
            setTimeout(() => this.run(), this.retryTime);
            return false;
        }

        // all docs where successfull, so we use the seq of the changes-fetch
        await setLastPushSequence(
            this.collection,
            this.endpointHash,
            changes.last_seq
        );

        if (changes.results.length === 0) {
            if (this.live) {
                // console.log('no more docs to push, wait for ping');
            } else {
                // console.log('RxGraphQLReplicationState._runPull(): no more docs to push and not live; complete = true');
            }
        } else {
            // we have more docs, re-run
            await this.runPush();
        }

        return true;
    }

    async handleDocumentFromRemote(doc, docsWithRevisions) {
        const deletedValue = doc[this.deletedFlag];
        const toPouch = this.collection._handleToPouch(doc);
        // console.log('handleDocumentFromRemote(' + toPouch._id + ') start');
        toPouch._deleted = deletedValue;
        delete toPouch[this.deletedFlag];
        const primaryValue = toPouch._id;

        const pouchState = docsWithRevisions[primaryValue];
        let newRevision = createRevisionForPulledDocument(
            this.endpointHash,
            toPouch
        );
        if (pouchState) {
            const newRevisionHeight = pouchState.revisions.start + 1;
            const revisionId = newRevision;
            newRevision = newRevisionHeight + '-' + newRevision;
            toPouch._revisions = {
                start: newRevisionHeight,
                ids: pouchState.revisions.ids
            };
            toPouch._revisions.ids.unshift(revisionId);
        } else {
            newRevision = '1-' + newRevision;
        }
        toPouch._rev = newRevision;

        await this.collection.pouch.bulkDocs(
            [
                toPouch
            ], {
                new_edits: false
            }
        );

        /**
         * because bulkDocs with new_edits: false
         * does not stream changes to the pouchdb,
         * we create the event and emit it,
         * so other instances get informed about it
         */
        const originalDoc = clone(toPouch);
        originalDoc._deleted = deletedValue;
        delete originalDoc[this.deletedFlag];
        originalDoc._rev = newRevision;
        const cE = changeEventfromPouchChange(
            originalDoc,
            this.collection
        );
        this.collection.$emit(cE);
    }

    cancel() {
        if (this.isStopped()) return;

        if (this.changesSub) this.changesSub.cancel();

        this._subjects.canceled.next(true);
        // TODO
    }
}

export function syncGraphQL({
    url,
    headers = {},
    waitForLeadership = true,
    pull,
    push,
    deletedFlag,
    live = false,
    liveInterval = 1000 * 10, // in ms
    retryTime = 1000 * 5, // in ms
    autoStart = true // if this is false, the replication does nothing at start
}) {
    const collection = this;

    // fill in defaults for pull & push
    if (pull) {
        if (!pull.modifier) pull.modifier = DEFAULT_MODIFIER;
    }
    if (push) {
        if (!push.modifier) push.modifier = DEFAULT_MODIFIER;
    }

    // ensure the collection is listening to plain-pouchdb writes
    collection.watchForChanges();

    const replicationState = new RxGraphQLReplicationState(
        collection,
        url,
        headers,
        pull,
        push,
        deletedFlag,
        live,
        liveInterval,
        retryTime
    );

    if (!autoStart) return replicationState;

    // run internal so .sync() does not have to be async
    const waitTillRun = waitForLeadership ? this.database.waitForLeadership() : promiseWait(0);
    waitTillRun.then(() => {

        // trigger run once
        replicationState.run();

        // start sync-interval
        if (replicationState.live) {

            if (pull) {
                (async () => {
                    while (!replicationState.isStopped()) {
                        await promiseWait(replicationState.liveInterval);
                        if (replicationState.isStopped()) return;
                        await replicationState.run();
                    }
                })();
            }

            if (push) {
                replicationState.changesSub = collection.pouch.changes({
                    since: 'now',
                    live: true,
                    include_docs: true
                }).on('change', function (change) {
                    if (replicationState.isStopped()) return;

                    const rev = change.doc._rev;
                    if (!wasRevisionfromPullReplication(
                        replicationState.endpointHash,
                        rev
                    )) {
                        replicationState.run();
                    }
                });

            }
        }
    });

    return replicationState;
}

export const rxdb = true;
export const prototypes = {
    RxCollection: proto => {
        proto.syncGraphQL = syncGraphQL;
    }
};

export default {
    rxdb,
    prototypes
};<|endoftext|>


<commit_before>var factory = require('./serializable-factory.js');

var EmailRule = function() {
  // private variables

  // public variables
  this.RuleTypes = {
    INSTANT: 'INSTANT',
    LATER: 'LATER',
    TRIGGER: 'TRIGGER'
  };

  this.ruleType = null;

  // The type is needed in a static and object context. There may be better ways to do this.
  this.TYPE = 'EmailRule';

  // INSTANT
  this.to = null;
  this.sheet = null;
  this.subject = null;
  this.body = null;

  // LATER
  this.laterDate = null;

  // TRIGGER
  this.sendColumn = null;
  this.timestampColumn = null;

  // ***** private methods ***** //

  // ***** privileged methods ***** //

  this.init = function() {
    this.ruleType = this.RuleTypes.INSTANT;
  };

  this.init();
};

/**
 * These types determine which type of rule any given rule is. They could be modeled as seperate classes.
 */
EmailRule.RuleTypes = {
  INSTANT: 'INSTANT',
  LATER: 'LATER',
  TRIGGER: 'TRIGGER'
};

/** The type is needed in a static and object context. There may be better ways to do this. */
EmailRule.TYPE = 'EmailRule';
factory.register(EmailRule);

/** */
module.exports = EmailRule;
<commit_msg>Forgot the constructor tag.<commit_after>var factory = require('./serializable-factory.js');

var EmailRule = function() {
  // private variables

  // public variables
  this.RuleTypes = {
    INSTANT: 'INSTANT',
    LATER: 'LATER',
    TRIGGER: 'TRIGGER'
  };

  this.ruleType = null;

  // The type is needed in a static and object context. There may be better ways to do this.
  this.TYPE = 'EmailRule';

  // INSTANT
  this.to = null;
  this.sheet = null;
  this.subject = null;
  this.body = null;

  // LATER
  this.laterDate = null;

  // TRIGGER
  this.sendColumn = null;
  this.timestampColumn = null;

  // ***** private methods ***** //

  // ***** privileged methods ***** //

  /**
   * @constructor
   */
  this.init = function() {
    this.ruleType = this.RuleTypes.INSTANT;
  };

  this.init();
};

/**
 * These types determine which type of rule any given rule is. They could be modeled as seperate classes.
 */
EmailRule.RuleTypes = {
  INSTANT: 'INSTANT',
  LATER: 'LATER',
  TRIGGER: 'TRIGGER'
};

/** The type is needed in a static and object context. There may be better ways to do this. */
EmailRule.TYPE = 'EmailRule';
factory.register(EmailRule);

/** */
module.exports = EmailRule;
<|endoftext|>


<commit_before>module.exports = function(grunt) {
  'use strict';

  require('time-grunt')(grunt);
  require('jit-grunt')(grunt)({
    customTasksDir: 'helpers/grunt/tasks'
  });

  // Initialize config
  grunt.initConfig({
    pkg: require('./package.json')
  });

  // load tasks config per file
  grunt.loadTasks('helpers/grunt/config');

  //--- grunt tasks

  grunt.registerTask('start', ['clean', 'lintspaces:all', 'newer:jshint']);

  grunt.registerTask('default', ['start']); // TODO: check grunt-prompt

  //---

  // @begin: build tasks
  grunt.registerTask('build', function(target) {
    if(target === 'dev') {
      return grunt.task.run([
        'start',
        'copy:dev_tobuild',
        'copy:dev_jstobuild',
        'copy:dev_vendortobuild',
        'cleanempty:build',
        'less:dev'
      ]);

    } else if(target === 'prod') {
      return  grunt.task.run([
        'start',
        'copy:prod_jstobuild',
        'html2js:prod',
        'rewriterequireconfig',
        'requirejs',
        'clean:build',
        'copy:prod_todist',
        'cleanempty:dist',
        'less:prod',
        'htmlmin',
        'imagemin',
        'uglify'
      ]);

    }
  });
  // @end: build tasks

  //---

  // @begin: dev tasks
    // @begin: dev with livereload
  grunt.registerTask('server:livereload', ['connect:livereload', 'watch:livereload']);
  grunt.registerTask('server:livereload:proxy', function() {
    grunt.loadNpmTasks('grunt-connect-proxy');
    return grunt.task.run([
      'configureProxies',
      'connect:livereloadProxy',
      'watch:livereload'
    ]);
  });

  grunt.registerTask('dev:livereload', ['build:dev','concurrent:livereload']);
  grunt.registerTask('dev:livereload:proxy', ['build:dev', 'concurrent:livereloadProxy']);
    // @end: dev with livereload

    // @begin: dev with browser sync
  grunt.registerTask('dev:sync', ['build:dev', 'browserSync:dev', 'watch:project']);
  grunt.registerTask('dev:sync:proxy', function() {
    grunt.loadNpmTasks('grunt-connect-proxy');
    return grunt.task.run([
      'build:dev',
      'configureProxies',
      'browserSync:devProxy',
      'watch:project'
    ]);
  });
    // @end: dev with browser sync

    // @begin: dev alias
  grunt.registerTask('dev', ['dev:livereload']);
  grunt.registerTask('dev:proxy', ['dev:livereload:proxy']);
    // @end: dev alias
  // @end: dev tasks

  //---

  // @begin: distribution preview tasks
  grunt.registerTask('dist', ['build:prod', 'connect:dist']);
  grunt.registerTask('dist:proxy', function() {
    grunt.loadNpmTasks('grunt-connect-proxy');
    return grunt.task.run([
      'build:prod',
      'configureProxies',
      'connect:distProxy'
    ]);
  });

    // @begin: preview with browser sync support
  grunt.registerTask('dist:sync', ['build:prod', 'browserSync:dist']);
  grunt.registerTask('dist:sync:proxy', function() {
    grunt.loadNpmTasks('grunt-connect-proxy');
    return grunt.task.run([
      'build:prod',
      'configureProxies',
      'browserSync:distProxy'
    ]);
  });
    // @end: preview with browser sync support

  // @end: distribution preview tasks

  //---

  //--- @begin: spec's tasks

  grunt.registerTask('coverage', ['karma:coverage', 'open:coverage']);

  //grunt.registerTask('specs:run:unit', ['karma:background:start', 'watch:unit']); // TODO: review :: needed?
  grunt.registerTask('specs:run:coverage', ['connect:coverage', 'watch:coverage']);
  grunt.registerTask('specs:run:reports', ['connect:reports', 'watch:reports']);

  grunt.registerTask('specs', ['lintspaces:all', 'newer:jshint', 'karma:coverage', 'concurrent:specs']);

  //--- @end: spec's tasks

};
<commit_msg>tools grunt update specs task #7<commit_after>module.exports = function(grunt) {
  'use strict';

  require('time-grunt')(grunt);
  require('jit-grunt')(grunt)({
    customTasksDir: 'helpers/grunt/tasks'
  });

  // Initialize config
  grunt.initConfig({
    pkg: require('./package.json')
  });

  // load tasks config per file
  grunt.loadTasks('helpers/grunt/config');

  //--- grunt tasks

  grunt.registerTask('start', ['clean', 'lintspaces:all', 'newer:jshint']);

  grunt.registerTask('default', ['start']); // TODO: check grunt-prompt

  //---

  // @begin: build tasks
  grunt.registerTask('build', function(target) {
    if(target === 'dev') {
      return grunt.task.run([
        'start',
        'copy:dev_tobuild',
        'copy:dev_jstobuild',
        'copy:dev_vendortobuild',
        'cleanempty:build',
        'less:dev'
      ]);

    } else if(target === 'prod') {
      return  grunt.task.run([
        'start',
        'copy:prod_jstobuild',
        'html2js:prod',
        'rewriterequireconfig',
        'requirejs',
        'clean:build',
        'copy:prod_todist',
        'cleanempty:dist',
        'less:prod',
        'htmlmin',
        'imagemin',
        'uglify'
      ]);

    }
  });
  // @end: build tasks

  //---

  // @begin: dev tasks
    // @begin: dev with livereload
  grunt.registerTask('server:livereload', ['connect:livereload', 'watch:livereload']);
  grunt.registerTask('server:livereload:proxy', function() {
    grunt.loadNpmTasks('grunt-connect-proxy');
    return grunt.task.run([
      'configureProxies',
      'connect:livereloadProxy',
      'watch:livereload'
    ]);
  });

  grunt.registerTask('dev:livereload', ['build:dev','concurrent:livereload']);
  grunt.registerTask('dev:livereload:proxy', ['build:dev', 'concurrent:livereloadProxy']);
    // @end: dev with livereload

    // @begin: dev with browser sync
  grunt.registerTask('dev:sync', ['build:dev', 'browserSync:dev', 'watch:project']);
  grunt.registerTask('dev:sync:proxy', function() {
    grunt.loadNpmTasks('grunt-connect-proxy');
    return grunt.task.run([
      'build:dev',
      'configureProxies',
      'browserSync:devProxy',
      'watch:project'
    ]);
  });
    // @end: dev with browser sync

    // @begin: dev alias
  grunt.registerTask('dev', ['dev:livereload']);
  grunt.registerTask('dev:proxy', ['dev:livereload:proxy']);
    // @end: dev alias
  // @end: dev tasks

  //---

  // @begin: distribution preview tasks
  grunt.registerTask('dist', ['build:prod', 'connect:dist']);
  grunt.registerTask('dist:proxy', function() {
    grunt.loadNpmTasks('grunt-connect-proxy');
    return grunt.task.run([
      'build:prod',
      'configureProxies',
      'connect:distProxy'
    ]);
  });

    // @begin: preview with browser sync support
  grunt.registerTask('dist:sync', ['build:prod', 'browserSync:dist']);
  grunt.registerTask('dist:sync:proxy', function() {
    grunt.loadNpmTasks('grunt-connect-proxy');
    return grunt.task.run([
      'build:prod',
      'configureProxies',
      'browserSync:distProxy'
    ]);
  });
    // @end: preview with browser sync support

  // @end: distribution preview tasks

  //---

  //--- @begin: spec's tasks

  grunt.registerTask('coverage', ['karma:coverage', 'open:coverage']);

  //grunt.registerTask('specs:run:unit', ['karma:background:start', 'watch:unit']); // TODO: review :: needed?
  grunt.registerTask('specs:run:coverage', ['connect:coverage', 'watch:coverage']);
  grunt.registerTask('specs:run:reports', [
    'connect:reports',
    'open:karma_report_coverage',
    'open:karma_report_jasmine',
    'watch:reports'
  ]);

  grunt.registerTask('specs', [
    'lintspaces:all', 'newer:jshint',
    'karma:coverage',
    'copy:karma_report_jasmine',
    'concurrent:specs'
  ]);

  //--- @end: spec's tasks

};
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

/* eslint-disable no-console, no-process-env */

var childProcess = require('child_process');
var parseArgs = require('minimist');
var path = require('path');
var ldj = require('ldjson-stream');
var fs = require('fs');
var util = require('util');
var process = require('process');
var console = require('console');
var setTimeout = require('timers').setTimeout;
var assert = require('assert');
var dgram = require('dgram');

var server = path.join(__dirname, 'bench_server.js');
var relay = path.join(__dirname, 'relay_server.js');
var trace = path.join(__dirname, 'trace_server.js');
var bench = path.join(__dirname, 'multi_bench.js');

process.stderr.setMaxListeners(Infinity);

var TorchCommand = ['sudo', 'torch'];
if (process.env.TORCH_COMMAND) {
    TorchCommand = process.env.TORCH_COMMAND.split(/\s+/);
}

module.exports = BenchmarkRunner;

function BenchmarkRunner(opts) {
    if (!(this instanceof BenchmarkRunner)) {
        return new BenchmarkRunner(opts);
    }

    var self = this;

    self.opts = opts;

    self.relayProcs = [];
    self.statsdServer = null;
    self.serverProcs = [];
    self.traceProc = null;
    self.benchProcs = [];
    self.benchCounter = 0;
    self.fileStream = null;
    self.startClientDelay = 1500;

    var SERVER_PORT = 7100;
    var TRACE_SERVER_PORT = 7039;
    var RELAY_SERVER_PORT = 7038;
    var RELAY_TRACE_PORT = 7037;
    var STATSD_PORT = 7036;
    var INSTANCE_COUNT = 72;
    var CLIENT_PORT = 7041;

    self.instanceCount = opts.instances || INSTANCE_COUNT;
    self.ports = {
        serverPort: SERVER_PORT,
        traceServerPort: TRACE_SERVER_PORT,
        relayServerPort: RELAY_SERVER_PORT,
        relayTraceServerPort: RELAY_TRACE_PORT,
        statsdPort: STATSD_PORT,
        clientPort: CLIENT_PORT
    };
    self.serviceName = 'benchmark';
}

BenchmarkRunner.prototype.start = function start() {
    var self = this;

    self.spawnSupportServices();

    self.spawnTargetServer();

    if (self.opts.relay || self.opts.trace) {
        setTimeout(startRelay, 1500);
    } else {
        setTimeout(startClient, self.startClientDelay);
    }

    function startRelay() {
        self.spawnRelayServer();

        setTimeout(startClient, self.startClientDelay);
    }

    function startClient() {
        self.spawnBenchmarkClient();

        if (self.opts.torch) {
            self.spawnTorchProcess();
        }
    }
};

BenchmarkRunner.prototype.spawnSupportServices =
function spawnSupportServices() {
    var self = this;

    self.startStatsd();
    self.openFileStream();
};

BenchmarkRunner.prototype.spawnTargetServer =
function spawnTargetServer() {
    var self = this;

    if (self.opts.multiProc) {
        self.startServer(self.ports.serverPort, 24);
        self.startServer(self.ports.serverPort + 24, 24);
        self.startServer(self.ports.serverPort + 48, 24);
    } else {
        self.startServer(self.ports.serverPort, self.instanceCount);
    }
};

BenchmarkRunner.prototype.spawnRelayServer =
function spawnRelayServer() {
    var self = this;

    self.startRelay('bench-relay');
    if (self.opts.trace) {
        self.startTraceServer();
        self.startRelay('trace-relay');
    }
};

BenchmarkRunner.prototype.spawnBenchmarkClient =
function spawnBenchmarkClient() {
    var self = this;

    if (self.opts.multiProc) {
        self.startClient(self.ports.clientPort);
        self.startClient(self.ports.clientPort + 200);
        self.startClient(self.ports.clientPort + 300);
    } else {
        self.startClient(self.ports.clientPort);
    }
};

BenchmarkRunner.prototype.spawnTorchProcess =
function spawnTorchProcess() {
    var self = this;

    if (self.opts.torch) {
        self.startTorch();
    }
};

BenchmarkRunner.prototype.startStatsd = function startStatsd() {
    var self = this;

    self.statsdServer = dgram.createSocket('udp4');
    self.statsdServer.bind(self.ports.statsdPort);
};

BenchmarkRunner.prototype.startServer =
function startServer(serverPort, instances) {
    var self = this;

    if (self.opts.goServer) {
        return self.startGoServer(serverPort, instances);
    }

    var noOverhead = self.opts.noEndpointOverhead;

    var serverProc = self.run(server, [
        self.opts.trace ? '--trace' : '--no-trace',
        '--traceRelayHostPort', '127.0.0.1:' + self.ports.relayTraceServerPort,
        '--port', String(serverPort),
        '--statsdPort', String(self.ports.statsdPort),
        '--instances', String(instances),
        '--pingOverhead', noOverhead ? 'none' : 'norm:10,5',
        '--setOverhead', noOverhead ? 'none' : 'norm:200,20',
        '--getOverhead', noOverhead ? 'none' : 'norm:100,10'
    ]);
    self.serverProcs.push(serverProc);
    serverProc.stdout.pipe(process.stderr);
    serverProc.stderr.pipe(process.stderr);
};

BenchmarkRunner.prototype.startGoServer =
function startGoServer(serverPort, instances) {
    var self = this;

    var serverProc = self.runExternal('../../golang/build/examples/bench/server', [
        '--host', 'localhost',
        '--port', String(serverPort),
        '--instances', String(instances)
    ]);
    self.serverProcs.push(serverProc);
    serverProc.stdout.pipe(process.stderr);
    serverProc.stderr.pipe(process.stderr);
};

BenchmarkRunner.prototype.startTraceServer =
function startTraceServer() {
    var self = this;

    self.traceProc = self.run(trace);
    self.traceProc.stdout.pipe(process.stderr);
    self.traceProc.stderr.pipe(process.stdout);
};

BenchmarkRunner.prototype.startRelay =
function startRelay(type) {
    var self = this;

    // type = 'bench-relay'
    var relayProc = self.run(relay, [
        '--benchPort', String(self.ports.serverPort),
        '--tracePort', String(self.ports.traceServerPort),
        '--benchRelayPort', String(self.ports.relayServerPort),
        '--traceRelayPort', String(self.ports.relayTraceServerPort),
        '--type', type,
        '--instances', String(self.instanceCount),
        self.opts.trace ? '--trace' : '--no-trace',
        self.opts.debug ? '--debug' : '--no-debug'
    ]);
    self.relayProcs.push(relayProc);
    relayProc.stdout.pipe(process.stderr);
    relayProc.stderr.pipe(process.stderr);
};

BenchmarkRunner.prototype.openFileStream =
function openFileStream() {
    var self = this;

    if (self.opts.output) {
        self.fileStream = fs.createWriteStream(self.opts.output, {
            encoding: 'utf8'
        });
    }
};

BenchmarkRunner.prototype.startClient =
function startClient(clientPort) {
    var self = this;

    self.benchCounter++;

    var args = self.opts['--'];
    args = args.concat([
        '--benchPort', String(self.ports.serverPort),
        '--relayServerPort', String(self.ports.relayServerPort),
        '--clientPort', String(clientPort),
        '--instanceNumber', String(self.benchCounter)
    ]);
    var benchProc = self.run(bench, args);
    self.benchProcs.push(benchProc);

    benchProc.stderr.pipe(process.stderr);

    benchProc.stdout
        .pipe(ldj.parse())
        .on('data', function onChunk(result) {
            console.log(util.format(
                '%s: %s, %s/%s min/max/avg/p95: %s/%s/%s/%s %sms total, %s ops/sec',
                String(result.instanceNumber),
                lpad(result.descr, 13),
                lpad(result.pipeline, 5),
                result.numClients,
                lpad(result.min, 4),
                lpad(result.max, 4),
                lpad(result.mean.toFixed(2), 7),
                lpad(result.p95.toFixed(2), 7),
                lpad(result.elapsed, 6),
                lpad(typeof result.rate === 'number' ?
                    result.rate.toFixed(2) : 'NaN', 8
                )
            ));

            if (self.fileStream) {
                self.fileStream.write(JSON.stringify(result) + '\n');
            }
        });

    benchProc.once('close', function onClose() {
        if (--self.benchCounter === 0) {
            self.close();
        }
    });

    benchProc.once('exit', function onExit(code) {
        if (code !== 0) {
            /* eslint no-process-exit: 0 */
            process.exit(code);
        }
    });
};

BenchmarkRunner.prototype.close = function close() {
    var self = this;
    var i;

    console.error('benchmark finished');
    for (i = 0; i < self.serverProcs.length; i++) {
        self.serverProcs[i].kill();
    }
    if (self.traceProc) {
        self.traceProc.kill();
    }

    if (self.fileStream) {
        self.fileStream.end();
    }

    for (i = 0; i < self.relayProcs.length; i++) {
        self.relayProcs[i].kill();
    }

    self.statsdServer.close();
};

BenchmarkRunner.prototype.torchCommand =
function torchCommand() {
    var self = this;

    assert(self.opts.torch === 'client' ||
           self.opts.torch === 'relay' ||
           self.opts.torch === 'server',
           'Torch flag must be client or relay'
    );
    assert(self.opts.torchFile, 'torchFile needed');

    var torchPid;
    var torchTime = self.opts.torchTime || '30';
    var torchType = self.opts.torchType || 'raw';
    var torchIndex = self.opts.torchIndex || 0;

    if (self.opts.torch === 'relay') {
        torchPid = self.relayProcs[torchIndex].pid;
    } else if (self.opts.torch === 'client') {
        torchPid = self.benchProcs[torchIndex].pid;
    } else if (self.opts.torch === 'server') {
        torchPid = self.serverProcs[torchIndex].pid;
    }

    return TorchCommand.concat([torchPid, torchType, torchTime]);
};

BenchmarkRunner.prototype.startTorch =
function startTorch() {
    var self = this;

    var torchFile = self.opts.torchFile;
    var torchDelay = self.opts.torchDelay || 10 * 1000;

    var cmd = self.torchCommand();

    setTimeout(function delayTorching() {
        var torchProc = childProcess.spawn(cmd[0], cmd.slice(1));
        torchProc.stdout.pipe(
            fs.createWriteStream(torchFile)
        );
        torchProc.stderr.pipe(process.stderr);
        console.error('starting flaming');

        torchProc.once('close', function onTorchClose() {
            console.error('finished flaming');
        });
    }, torchDelay);
};

BenchmarkRunner.prototype.run = function run(script, args) {
    var name = script.replace(/\.js$/, '');
    args = args ? args.slice(0) : [];
    args.unshift(script);
    var child = childProcess.spawn(process.execPath, args);
    child.on('exit', function exited(code, signal) {
        console.error('child exited %s[%s] code %s signal %s',
                      name, child.pid, code, signal);
    });
    console.error('running %s[%s]', name, child.pid);
    return child;
};

BenchmarkRunner.prototype.runExternal = function runExternal(cmd, args) {
    var child = childProcess.spawn(cmd, args);
    console.error('running %s[%s]', cmd, child.pid);
    return child;
};

if (require.main === module) {
    var argv = parseArgs(process.argv.slice(2), {
        '--': true,
        alias: {
            o: 'output'
        },
        boolean: ['relay', 'trace', 'debug', 'noEndpointOverhead']
    });
    var runner = BenchmarkRunner(argv);
    runner.start();
}

function lpad(input, len, chr) {
    var str = input.toString();
    chr = chr || ' ';

    while (str.length < len) {
        str = chr + str;
    }
    return str;
}
<commit_msg>BenchmarkRunner#torchCommand: decouple arge calling convention<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

/* eslint-disable no-console, no-process-env */

var childProcess = require('child_process');
var parseArgs = require('minimist');
var path = require('path');
var ldj = require('ldjson-stream');
var fs = require('fs');
var util = require('util');
var process = require('process');
var console = require('console');
var setTimeout = require('timers').setTimeout;
var assert = require('assert');
var dgram = require('dgram');

var server = path.join(__dirname, 'bench_server.js');
var relay = path.join(__dirname, 'relay_server.js');
var trace = path.join(__dirname, 'trace_server.js');
var bench = path.join(__dirname, 'multi_bench.js');

process.stderr.setMaxListeners(Infinity);

var TorchCommand = ['sudo', 'torch', '{pid}', 'raw', '{time}'];
if (process.env.TORCH_COMMAND) {
    TorchCommand = process.env.TORCH_COMMAND.split(/\s+/);
}

module.exports = BenchmarkRunner;

function BenchmarkRunner(opts) {
    if (!(this instanceof BenchmarkRunner)) {
        return new BenchmarkRunner(opts);
    }

    var self = this;

    self.opts = opts;

    self.relayProcs = [];
    self.statsdServer = null;
    self.serverProcs = [];
    self.traceProc = null;
    self.benchProcs = [];
    self.benchCounter = 0;
    self.fileStream = null;
    self.startClientDelay = 1500;

    var SERVER_PORT = 7100;
    var TRACE_SERVER_PORT = 7039;
    var RELAY_SERVER_PORT = 7038;
    var RELAY_TRACE_PORT = 7037;
    var STATSD_PORT = 7036;
    var INSTANCE_COUNT = 72;
    var CLIENT_PORT = 7041;

    self.instanceCount = opts.instances || INSTANCE_COUNT;
    self.ports = {
        serverPort: SERVER_PORT,
        traceServerPort: TRACE_SERVER_PORT,
        relayServerPort: RELAY_SERVER_PORT,
        relayTraceServerPort: RELAY_TRACE_PORT,
        statsdPort: STATSD_PORT,
        clientPort: CLIENT_PORT
    };
    self.serviceName = 'benchmark';
}

BenchmarkRunner.prototype.start = function start() {
    var self = this;

    self.spawnSupportServices();

    self.spawnTargetServer();

    if (self.opts.relay || self.opts.trace) {
        setTimeout(startRelay, 1500);
    } else {
        setTimeout(startClient, self.startClientDelay);
    }

    function startRelay() {
        self.spawnRelayServer();

        setTimeout(startClient, self.startClientDelay);
    }

    function startClient() {
        self.spawnBenchmarkClient();

        if (self.opts.torch) {
            self.spawnTorchProcess();
        }
    }
};

BenchmarkRunner.prototype.spawnSupportServices =
function spawnSupportServices() {
    var self = this;

    self.startStatsd();
    self.openFileStream();
};

BenchmarkRunner.prototype.spawnTargetServer =
function spawnTargetServer() {
    var self = this;

    if (self.opts.multiProc) {
        self.startServer(self.ports.serverPort, 24);
        self.startServer(self.ports.serverPort + 24, 24);
        self.startServer(self.ports.serverPort + 48, 24);
    } else {
        self.startServer(self.ports.serverPort, self.instanceCount);
    }
};

BenchmarkRunner.prototype.spawnRelayServer =
function spawnRelayServer() {
    var self = this;

    self.startRelay('bench-relay');
    if (self.opts.trace) {
        self.startTraceServer();
        self.startRelay('trace-relay');
    }
};

BenchmarkRunner.prototype.spawnBenchmarkClient =
function spawnBenchmarkClient() {
    var self = this;

    if (self.opts.multiProc) {
        self.startClient(self.ports.clientPort);
        self.startClient(self.ports.clientPort + 200);
        self.startClient(self.ports.clientPort + 300);
    } else {
        self.startClient(self.ports.clientPort);
    }
};

BenchmarkRunner.prototype.spawnTorchProcess =
function spawnTorchProcess() {
    var self = this;

    if (self.opts.torch) {
        self.startTorch();
    }
};

BenchmarkRunner.prototype.startStatsd = function startStatsd() {
    var self = this;

    self.statsdServer = dgram.createSocket('udp4');
    self.statsdServer.bind(self.ports.statsdPort);
};

BenchmarkRunner.prototype.startServer =
function startServer(serverPort, instances) {
    var self = this;

    if (self.opts.goServer) {
        return self.startGoServer(serverPort, instances);
    }

    var noOverhead = self.opts.noEndpointOverhead;

    var serverProc = self.run(server, [
        self.opts.trace ? '--trace' : '--no-trace',
        '--traceRelayHostPort', '127.0.0.1:' + self.ports.relayTraceServerPort,
        '--port', String(serverPort),
        '--statsdPort', String(self.ports.statsdPort),
        '--instances', String(instances),
        '--pingOverhead', noOverhead ? 'none' : 'norm:10,5',
        '--setOverhead', noOverhead ? 'none' : 'norm:200,20',
        '--getOverhead', noOverhead ? 'none' : 'norm:100,10'
    ]);
    self.serverProcs.push(serverProc);
    serverProc.stdout.pipe(process.stderr);
    serverProc.stderr.pipe(process.stderr);
};

BenchmarkRunner.prototype.startGoServer =
function startGoServer(serverPort, instances) {
    var self = this;

    var serverProc = self.runExternal('../../golang/build/examples/bench/server', [
        '--host', 'localhost',
        '--port', String(serverPort),
        '--instances', String(instances)
    ]);
    self.serverProcs.push(serverProc);
    serverProc.stdout.pipe(process.stderr);
    serverProc.stderr.pipe(process.stderr);
};

BenchmarkRunner.prototype.startTraceServer =
function startTraceServer() {
    var self = this;

    self.traceProc = self.run(trace);
    self.traceProc.stdout.pipe(process.stderr);
    self.traceProc.stderr.pipe(process.stdout);
};

BenchmarkRunner.prototype.startRelay =
function startRelay(type) {
    var self = this;

    // type = 'bench-relay'
    var relayProc = self.run(relay, [
        '--benchPort', String(self.ports.serverPort),
        '--tracePort', String(self.ports.traceServerPort),
        '--benchRelayPort', String(self.ports.relayServerPort),
        '--traceRelayPort', String(self.ports.relayTraceServerPort),
        '--type', type,
        '--instances', String(self.instanceCount),
        self.opts.trace ? '--trace' : '--no-trace',
        self.opts.debug ? '--debug' : '--no-debug'
    ]);
    self.relayProcs.push(relayProc);
    relayProc.stdout.pipe(process.stderr);
    relayProc.stderr.pipe(process.stderr);
};

BenchmarkRunner.prototype.openFileStream =
function openFileStream() {
    var self = this;

    if (self.opts.output) {
        self.fileStream = fs.createWriteStream(self.opts.output, {
            encoding: 'utf8'
        });
    }
};

BenchmarkRunner.prototype.startClient =
function startClient(clientPort) {
    var self = this;

    self.benchCounter++;

    var args = self.opts['--'];
    args = args.concat([
        '--benchPort', String(self.ports.serverPort),
        '--relayServerPort', String(self.ports.relayServerPort),
        '--clientPort', String(clientPort),
        '--instanceNumber', String(self.benchCounter)
    ]);
    var benchProc = self.run(bench, args);
    self.benchProcs.push(benchProc);

    benchProc.stderr.pipe(process.stderr);

    benchProc.stdout
        .pipe(ldj.parse())
        .on('data', function onChunk(result) {
            console.log(util.format(
                '%s: %s, %s/%s min/max/avg/p95: %s/%s/%s/%s %sms total, %s ops/sec',
                String(result.instanceNumber),
                lpad(result.descr, 13),
                lpad(result.pipeline, 5),
                result.numClients,
                lpad(result.min, 4),
                lpad(result.max, 4),
                lpad(result.mean.toFixed(2), 7),
                lpad(result.p95.toFixed(2), 7),
                lpad(result.elapsed, 6),
                lpad(typeof result.rate === 'number' ?
                    result.rate.toFixed(2) : 'NaN', 8
                )
            ));

            if (self.fileStream) {
                self.fileStream.write(JSON.stringify(result) + '\n');
            }
        });

    benchProc.once('close', function onClose() {
        if (--self.benchCounter === 0) {
            self.close();
        }
    });

    benchProc.once('exit', function onExit(code) {
        if (code !== 0) {
            /* eslint no-process-exit: 0 */
            process.exit(code);
        }
    });
};

BenchmarkRunner.prototype.close = function close() {
    var self = this;
    var i;

    console.error('benchmark finished');
    for (i = 0; i < self.serverProcs.length; i++) {
        self.serverProcs[i].kill();
    }
    if (self.traceProc) {
        self.traceProc.kill();
    }

    if (self.fileStream) {
        self.fileStream.end();
    }

    for (i = 0; i < self.relayProcs.length; i++) {
        self.relayProcs[i].kill();
    }

    self.statsdServer.close();
};

BenchmarkRunner.prototype.torchCommand =
function torchCommand() {
    var self = this;

    assert(self.opts.torch === 'client' ||
           self.opts.torch === 'relay' ||
           self.opts.torch === 'server',
           'Torch flag must be client or relay'
    );
    assert(self.opts.torchFile, 'torchFile needed');

    var torchPid;
    var torchTime = self.opts.torchTime || '30';
    var torchIndex = self.opts.torchIndex || 0;

    if (self.opts.torch === 'relay') {
        torchPid = self.relayProcs[torchIndex].pid;
    } else if (self.opts.torch === 'client') {
        torchPid = self.benchProcs[torchIndex].pid;
    } else if (self.opts.torch === 'server') {
        torchPid = self.serverProcs[torchIndex].pid;
    }

    return TorchCommand.map(function each(part) {
        if (part === '{pid}') {
            return torchPid;
        } else if (part === '{time}') {
            return torchTime;
        } else {
            return part;
        }
    });
};

BenchmarkRunner.prototype.startTorch =
function startTorch() {
    var self = this;

    var torchFile = self.opts.torchFile;
    var torchDelay = self.opts.torchDelay || 10 * 1000;

    var cmd = self.torchCommand();

    setTimeout(function delayTorching() {
        var torchProc = childProcess.spawn(cmd[0], cmd.slice(1));
        torchProc.stdout.pipe(
            fs.createWriteStream(torchFile)
        );
        torchProc.stderr.pipe(process.stderr);
        console.error('starting flaming');

        torchProc.once('close', function onTorchClose() {
            console.error('finished flaming');
        });
    }, torchDelay);
};

BenchmarkRunner.prototype.run = function run(script, args) {
    var name = script.replace(/\.js$/, '');
    args = args ? args.slice(0) : [];
    args.unshift(script);
    var child = childProcess.spawn(process.execPath, args);
    child.on('exit', function exited(code, signal) {
        console.error('child exited %s[%s] code %s signal %s',
                      name, child.pid, code, signal);
    });
    console.error('running %s[%s]', name, child.pid);
    return child;
};

BenchmarkRunner.prototype.runExternal = function runExternal(cmd, args) {
    var child = childProcess.spawn(cmd, args);
    console.error('running %s[%s]', cmd, child.pid);
    return child;
};

if (require.main === module) {
    var argv = parseArgs(process.argv.slice(2), {
        '--': true,
        alias: {
            o: 'output'
        },
        boolean: ['relay', 'trace', 'debug', 'noEndpointOverhead']
    });
    var runner = BenchmarkRunner(argv);
    runner.start();
}

function lpad(input, len, chr) {
    var str = input.toString();
    chr = chr || ' ';

    while (str.length < len) {
        str = chr + str;
    }
    return str;
}
<|endoftext|>


<commit_before>// eslint-disable-next-line no-global-assign
require = require('esm')(module)
const fs = require('fs')
const chalk = require('chalk')
const Constants = require('../src/constants/index.js').default
let errorSum = 0

class API {
  constructor () {
    this.init()
    this.check()
  }

  check () {
    const file = `../site/docs/api/${this.file}`
    const md = {}
    const content = fs.readFileSync(file).toString()
    const lines = content.split('## ')
    const outLines = lines.slice(0, 1)
    const errors = []

    for (const item of lines.slice(1)) {
      md[item.split('\n')[0]] = item
    }

    const mds = Object.keys(md)
    for (const [i, key] of this.options.entries()) {
      if (md[key]) {
        outLines.push(md[key])
        const details = md[key].split('\n\n- ')

        for (let i = 0; i < this.attributes.length; i++) {
          const name = this.attributes[i]
          if (this.ignore && this.ignore[key] && this.ignore[key].includes(name)) {
            continue
          }
          if (!details[i + 1] || details[i + 1].indexOf(`**${name}:**`) === -1) {
            errors.push(chalk.red(`[${key}] missing '${name}'`))
          }
        }
      } else {
        outLines.push(key + '\n\n')
      }
    }

    errorSum += errors.length
    if (errors.length > 0) {
      console.log('-------------------------')
      console.log(`Checking file: ${file}`)
      console.log('-------------------------')

      errors.forEach((error) => {
        console.log(error)
      })
    }

    fs.writeFile(file, outLines.join('## '), () => {})
  }
}

class TableOptions extends API {
  init () {
    this.file = 'table-options.md'
    this.options = Object.keys(Constants.DEFAULTS).filter(it => {
      return !/^(on|format)[A-Z]/.test(it)
    })
    this.options.unshift('-')
    this.attributes = ['Attribute', 'Type', 'Detail', 'Default', 'Example']
    this.ignore = {
      totalRows: ['Example'],
      totalNotFiltered: ['Example']
    }
  }
}

class ColumnOptions extends API {
  init () {
    this.file = 'column-options.md'
    this.options = Object.keys(Constants.COLUMN_DEFAULTS)
    this.attributes = ['Attribute', 'Type', 'Detail', 'Default', 'Example']
  }
}

class Methods extends API {
  init () {
    this.file = 'methods.md'
    this.options = Constants.METHODS
    this.attributes = ['Parameter', 'Detail', 'Example']
    this.ignore = {
      resetWidth: ['Example']
    }
  }
}

class Events extends API {
  init () {
    this.file = 'events.md'
    this.options = Object.values(Constants.EVENTS)
    this.attributes = ['jQuery Event', 'Parameter', 'Detail']
  }
}

class Localizations extends API {
  init () {
    this.file = 'localizations.md'
    this.options = Object.keys(Constants.LOCALES.en)
    this.attributes = ['Parameter', 'Default']
  }
}

new TableOptions()
new ColumnOptions()
new Methods()
new Events()
new Localizations()

if (errorSum === 0) {
  console.log('Good job! Anything up to date!')
  process.exit(0)
}

process.exit(1)<commit_msg>use the sync function instead of the async function<commit_after>// eslint-disable-next-line no-global-assign
require = require('esm')(module)
const fs = require('fs')
const chalk = require('chalk')
const Constants = require('../src/constants/index.js').default
let errorSum = 0

class API {
  constructor () {
    this.init()
    this.check()
  }

  check () {
    const file = `../site/docs/api/${this.file}`
    const md = {}
    const content = fs.readFileSync(file).toString()
    const lines = content.split('## ')
    const outLines = lines.slice(0, 1)
    const errors = []

    for (const item of lines.slice(1)) {
      md[item.split('\n')[0]] = item
    }

    const mds = Object.keys(md)
    for (const [i, key] of this.options.entries()) {
      if (md[key]) {
        outLines.push(md[key])
        const details = md[key].split('\n\n- ')

        for (let i = 0; i < this.attributes.length; i++) {
          const name = this.attributes[i]
          if (this.ignore && this.ignore[key] && this.ignore[key].includes(name)) {
            continue
          }
          if (!details[i + 1] || details[i + 1].indexOf(`**${name}:**`) === -1) {
            errors.push(chalk.red(`[${key}] missing '${name}'`))
          }
        }
      } else {
        outLines.push(key + '\n\n')
      }
    }

    errorSum += errors.length
    if (errors.length > 0) {
      console.log('-------------------------')
      console.log(`Checking file: ${file}`)
      console.log('-------------------------')

      errors.forEach((error) => {
        console.log(error)
      })
    }
    fs.writeFileSync(file, outLines.join('## '), () => {})
  }
}

class TableOptions extends API {
  init () {
    this.file = 'table-options.md'
    this.options = Object.keys(Constants.DEFAULTS).filter(it => {
      return !/^(on|format)[A-Z]/.test(it)
    })
    this.options.unshift('-')
    this.attributes = ['Attribute', 'Type', 'Detail', 'Default', 'Example']
    this.ignore = {
      totalRows: ['Example'],
      totalNotFiltered: ['Example']
    }
  }
}

class ColumnOptions extends API {
  init () {
    this.file = 'column-options.md'
    this.options = Object.keys(Constants.COLUMN_DEFAULTS)
    this.attributes = ['Attribute', 'Type', 'Detail', 'Default', 'Example']
  }
}

class Methods extends API {
  init () {
    this.file = 'methods.md'
    this.options = Constants.METHODS
    this.attributes = ['Parameter', 'Detail', 'Example']
    this.ignore = {
      resetWidth: ['Example']
    }
  }
}

class Events extends API {
  init () {
    this.file = 'events.md'
    this.options = Object.values(Constants.EVENTS)
    this.attributes = ['jQuery Event', 'Parameter', 'Detail']
  }
}

class Localizations extends API {
  init () {
    this.file = 'localizations.md'
    this.options = Object.keys(Constants.LOCALES.en)
    this.attributes = ['Parameter', 'Default']
  }
}

new TableOptions()
new ColumnOptions()
new Methods()
new Events()
new Localizations()

if (errorSum === 0) {
  console.log('Good job! Anything up to date!')
  process.exit(0)
}

process.exit(1)<|endoftext|>


<commit_before>define([
    'durandal/app',
    'knockout',
    'mods/portal',
    'mods/state',
    'factory/object',
    'mods/xmlmanager',
    'mods/jsonformfields',
    'factory/metadata',
    'mods/format',
    'mods/player',
    'mods/timeline',
    'mods/objectmanager'
],
        function (app, ko, portal, state, objfac, xmlman,
            jsonformfields, metadatafac, format, player, timeline, objectmanager) {

            var obj = {};
            obj.guid;
            obj.data;
            obj.anndata;
            obj.anndatacount;

            var title = ko.observable();
            var channel = ko.observable();
            var publication = ko.observable();
            var abstracttxt = ko.observable();
            var description = ko.observable();

            var playerposition = ko.observable(0);

            var metadataViews = ko.observableArray();
            var metadataEditors = ko.observableArray();

            app.on('metadata:save').then(function (e) {
                CHAOS.Portal.Client.Metadata.Set(
                e.guid, e.schemaguid, "da",
                1, e.xml, null).WithCallback(metadataSaved);

            })

            function metadataSaved(r) {

                if (r.Error != null) {
                    app.showMessage(r.Error.Message, "Error saving", ["OK", "Cancel"]);
                    return;
                }

                metadataEditors.removeAll();
            }

            app.on('metadata:changed_timeline').then(function (e) {
                var d = e.data;
                if (metadataEditors().length == 1) {
                    var ed = metadataEditors()[0];
                    if (ed.data.guid == d.id) {
                        ed.data.self.starttime(format.getTimeStringFromDate(d.start));
                        ed.data.self.endtime(format.getTimeStringFromDate(d.end));
                    }
                }
            });

            app.on('metadata:changed_editor').then(function (e) {
                var dat = timeline.getSelection();
                if (dat == undefined)
                    return;

                if (metadataEditors().length == 1) {
                    var ed = metadataEditors()[0];
                    if (ed.data.guid == dat.id) {
                        var s = ed.data.self.starttime();
                        var e = ed.data.self.endtime();
                        var t = ed.data.self.title();

                        if (s == "" || e == "")
                            return;

                        var timestart = timeline.start() + format.getMillisecondsFromString(s);
                        var timeend = timeline.start() + format.getMillisecondsFromString(e);
                        var content = '
 ' + t + '
'
                        timeline.changeItem(new Date(timestart), new Date(timeend), content);
                    }
                }
            });

            app.on('metadata:edit').then(function (editorvm) {
                if (editorvm.data === undefined)
                    return;
                var d = editorvm.data;

                objectmanager.getByGuid(d.Id, function (r) {

                    metadataEditors.removeAll();
                    var mds = r.Metadatas;
                    for (var i = 0; i < mds.length; i++) {
                        if (mds[i].MetadataSchemaGuid == 'd0edf6f9-caf0-ac41-b8b3-b0d950fdef4e') {
                            var editor = new metadatafac.MetadataView();
                            editor.setview("anncommentedit", {guid: r.Id, metadata: mds[i]});
                            metadataEditors.push(editor)
                        }
                    }

                });

                /*
                metadataEditors.removeAll();
                for (var i = 0; i < metadataViews().length; i++) {
                    var md = metadataViews()[i];
                    if (md.data == editorvm.data) {
                        var annview = new metadatafac.MetadataView();
                        annview.setview("annotationedit", md.data);
                        metadataEditors.push(annview);
                    }
                }
                */
            });

            // Getting data from API.
            function metadataReceived(data) {
                obj.data = data;
                var r = data.Body.Results[0];
                var mdsguid = state.searchMetadataSchemaGuids[0];
                if (r == undefined) {
                    app.showMessage("The data is not available for this object.", "Data missing", ["OK", "Cancel"]);
                    return;
                }
                if (r.Metadatas == undefined) {
                    app.showMessage("The metadata is not available for this object.", "Metadata missing", ["OK", "Cancel"]);
                    return;
                }

                timeline.state.subscribe(function (state) {
                    insertAnnotations();
                });

                // Initiate Player and callback functions
                player.init(r);
                player.position.subscribe(function (position) {
                    playerposition(position);
                    timeline.setPosition(playerposition());
                });
                player.duration.subscribe(function (duration) {
                    timeline.init(duration);
                    insertAnnotations();
                });

                // Get common metadata for the object
                for (var j = 0; j < r.Metadatas.length; j++) {
                    if (r.Metadatas[j].MetadataSchemaGuid == mdsguid) {
                        obj.metadataSchemaGuid = mdsguid;
                        var xml = r.Metadatas[j].MetadataXml;
                        var x = xmlman.parseXml(xml);
                        obj.metadata = x;

                        title($(x).find("Title").text());
                        channel($(x).find("PublicationChannel").text());
                        publication($(x).find("PublicationDateTime").text());
                        abstracttxt($(x).find("Abstract").text());
                        description($(x).find("Description").text());
                    }
                }
            }

            function loadAnnotations() {

            }

            function annotationsReceived(response) {
                obj.anndatacount = response.Body.Count;
                if (obj.anndatacount > 0)
                    obj.anndata = response.Body.Results;

                insertAnnotations();

            }

            var annotationsHaveBeenInserted = false;
            function insertAnnotations() {

                if (annotationsHaveBeenInserted)
                    return;

                if (obj.anndata === undefined || !player.isReady() || !timeline.isReady())
                    return;

                annotationsHaveBeenInserted = true;

                var dataarray = [];
                var amds = obj.anndata;
                for (var i = 0; i < amds.length; i++) {
                    var amd = amds[i];
                    var content = '
 ' + amd.Title + '
'

                    var timestart = format.getSecondsFromString(amd.StartTime);
                    var timeend = format.getSecondsFromString(amd.EndTime);
                    timestart = player.getProgramTimeFromFileTime(timestart);
                    timeend = player.getProgramTimeFromFileTime(timeend);
                    timestart = timeline.start() + timestart * 1000;
                    timeend = timeline.start() + timeend * 1000;

                    // not editable
                    dataarray.push([new Date(timestart), new Date(timeend), content, false, amd.Id]);

                    var annview = new metadatafac.MetadataView();
                    annview.setview("annotation", amd);
                    metadataViews.push(annview);

                }

                timeline.addData(dataarray);

            }

            return {
                mediaUrl: player.mediaUrl,
                title: title,
                channel: channel,
                publication: publication,
                abstracttxt: abstracttxt,
                description: description,

                metadataViews: metadataViews,
                metadataEditors: metadataEditors,

                activate: function (param) {
                    if (param !== undefined) {
                        var id = format.getParamByName('id', param);
                        obj.guid = id;
                        var objguids = [];
                        objguids.push(id);

                        // Object Get
                        CHAOS.Portal.Client.Object.Get(
                            objguids, Settings.accessPointGuid, true, true,
                            true, false, false,
                            1, 0, null).WithCallback(metadataReceived);

                        // Annotation View
                        CHAOS.Portal.Client.View.Get(
                            'Annotation', 'ProgramGUID:"' + obj.guid + '"',
                            'StartTime+ASC', null, 0, 9999).WithCallback(annotationsReceived);

                    }
                },
                play: function () {
                    player.play();
                },
                pause: function () {
                    player.pause();
                }
            };
        });

<commit_msg>Annotation color.<commit_after>define([
    'durandal/app',
    'knockout',
    'mods/portal',
    'mods/state',
    'factory/object',
    'mods/xmlmanager',
    'mods/jsonformfields',
    'factory/metadata',
    'mods/format',
    'mods/player',
    'mods/timeline',
    'mods/objectmanager'
],
        function (app, ko, portal, state, objfac, xmlman,
            jsonformfields, metadatafac, format, player, timeline, objectmanager) {

            var obj = {};
            obj.guid;
            obj.data;
            obj.anndata;
            obj.anndatacount;

            var title = ko.observable();
            var channel = ko.observable();
            var publication = ko.observable();
            var abstracttxt = ko.observable();
            var description = ko.observable();

            var playerposition = ko.observable(0);

            var metadataViews = ko.observableArray();
            var metadataEditors = ko.observableArray();

            app.on('metadata:save').then(function (e) {
                CHAOS.Portal.Client.Metadata.Set(
                e.guid, e.schemaguid, "da",
                1, e.xml, null).WithCallback(metadataSaved);

            })

            function metadataSaved(r) {

                if (r.Error != null) {
                    app.showMessage(r.Error.Message, "Error saving", ["OK", "Cancel"]);
                    return;
                }

                metadataEditors.removeAll();
            }

            app.on('metadata:changed_timeline').then(function (e) {
                var d = e.data;
                if (metadataEditors().length == 1) {
                    var ed = metadataEditors()[0];
                    if (ed.data.guid == d.id) {
                        ed.data.self.starttime(format.getTimeStringFromDate(d.start));
                        ed.data.self.endtime(format.getTimeStringFromDate(d.end));
                    }
                }
            });

            app.on('metadata:changed_editor').then(function (e) {
                var dat = timeline.getSelection();
                if (dat == undefined)
                    return;

                if (metadataEditors().length == 1) {
                    var ed = metadataEditors()[0];
                    if (ed.data.guid == dat.id) {
                        var s = ed.data.self.starttime();
                        var e = ed.data.self.endtime();
                        var t = ed.data.self.title();

                        if (s == "" || e == "")
                            return;

                        var timestart = timeline.start() + format.getMillisecondsFromString(s);
                        var timeend = timeline.start() + format.getMillisecondsFromString(e);
                        var content = '
 ' + t + '
'
                        timeline.changeItem(new Date(timestart), new Date(timeend), content);
                    }
                }
            });

            app.on('metadata:edit').then(function (editorvm) {
                if (editorvm.data === undefined)
                    return;
                var d = editorvm.data;

                objectmanager.getByGuid(d.Id, function (r) {

                    metadataEditors.removeAll();
                    var mds = r.Metadatas;
                    for (var i = 0; i < mds.length; i++) {
                        if (mds[i].MetadataSchemaGuid == 'd0edf6f9-caf0-ac41-b8b3-b0d950fdef4e') {
                            var editor = new metadatafac.MetadataView();
                            editor.setview("anncommentedit", {guid: r.Id, metadata: mds[i]});
                            metadataEditors.push(editor)
                        }
                    }

                });

                /*
                metadataEditors.removeAll();
                for (var i = 0; i < metadataViews().length; i++) {
                    var md = metadataViews()[i];
                    if (md.data == editorvm.data) {
                        var annview = new metadatafac.MetadataView();
                        annview.setview("annotationedit", md.data);
                        metadataEditors.push(annview);
                    }
                }
                */
            });

            // Getting data from API.
            function metadataReceived(data) {
                obj.data = data;
                var r = data.Body.Results[0];
                var mdsguid = state.searchMetadataSchemaGuids[0];
                if (r == undefined) {
                    app.showMessage("The data is not available for this object.", "Data missing", ["OK", "Cancel"]);
                    return;
                }
                if (r.Metadatas == undefined) {
                    app.showMessage("The metadata is not available for this object.", "Metadata missing", ["OK", "Cancel"]);
                    return;
                }

                timeline.state.subscribe(function (state) {
                    insertAnnotations();
                });

                // Initiate Player and callback functions
                player.init(r);
                player.position.subscribe(function (position) {
                    playerposition(position);
                    timeline.setPosition(playerposition());
                });
                player.duration.subscribe(function (duration) {
                    timeline.init(duration);
                    insertAnnotations();
                });

                // Get common metadata for the object
                for (var j = 0; j < r.Metadatas.length; j++) {
                    if (r.Metadatas[j].MetadataSchemaGuid == mdsguid) {
                        obj.metadataSchemaGuid = mdsguid;
                        var xml = r.Metadatas[j].MetadataXml;
                        var x = xmlman.parseXml(xml);
                        obj.metadata = x;

                        title($(x).find("Title").text());
                        channel($(x).find("PublicationChannel").text());
                        publication($(x).find("PublicationDateTime").text());
                        abstracttxt($(x).find("Abstract").text());
                        description($(x).find("Description").text());
                    }
                }
            }

            function loadAnnotations() {

            }

            function annotationsReceived(response) {
                obj.anndatacount = response.Body.Count;
                if (obj.anndatacount > 0)
                    obj.anndata = response.Body.Results;

                insertAnnotations();

            }

            var annotationsHaveBeenInserted = false;
            function insertAnnotations() {

                if (annotationsHaveBeenInserted)
                    return;

                if (obj.anndata === undefined || !player.isReady() || !timeline.isReady())
                    return;

                annotationsHaveBeenInserted = true;

                var dataarray = [];
                var amds = obj.anndata;
                for (var i = 0; i < amds.length; i++) {
                    var amd = amds[i];
                    var content = '
 ' + amd.Title + '
'

                    var timestart = format.getSecondsFromString(amd.StartTime);
                    var timeend = format.getSecondsFromString(amd.EndTime);
                    timestart = player.getProgramTimeFromFileTime(timestart);
                    timeend = player.getProgramTimeFromFileTime(timeend);
                    timestart = timeline.start() + timestart * 1000;
                    timeend = timeline.start() + timeend * 1000;

                    // not editable
                    dataarray.push([new Date(timestart), new Date(timeend), content, false, amd.Id]);

                    var annview = new metadatafac.MetadataView();
                    annview.setview("annotation", amd);
                    metadataViews.push(annview);

                }

                timeline.addData(dataarray);

            }

            return {
                mediaUrl: player.mediaUrl,
                title: title,
                channel: channel,
                publication: publication,
                abstracttxt: abstracttxt,
                description: description,

                metadataViews: metadataViews,
                metadataEditors: metadataEditors,

                activate: function (param) {
                    if (param !== undefined) {
                        var id = format.getParamByName('id', param);
                        obj.guid = id;
                        var objguids = [];
                        objguids.push(id);

                        // Object Get
                        CHAOS.Portal.Client.Object.Get(
                            objguids, Settings.accessPointGuid, true, true,
                            true, false, false,
                            1, 0, null).WithCallback(metadataReceived);

                        // Annotation View
                        CHAOS.Portal.Client.View.Get(
                            'Annotation', 'ProgramGUID:"' + obj.guid + '"',
                            'StartTime+ASC', null, 0, 9999).WithCallback(annotationsReceived);

                    }
                },
                play: function () {
                    player.play();
                },
                pause: function () {
                    player.pause();
                }
            };
        });

<|endoftext|>