diff --git a/.gitignore b/.gitignore index 40c0619e..d52b66ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ node_modules -.jshintrc -Procfile +away/* diff --git a/.jslint.conf b/.jslint.conf new file mode 100644 index 00000000..2976be24 --- /dev/null +++ b/.jslint.conf @@ -0,0 +1,144 @@ +# +# Configuration File for JavaScript Lint 0.3.0 +# Developed by Matthias Miller (http://www.JavaScriptLint.com) +# +# This configuration file can be used to lint a collection of scripts, or to enable +# or disable warnings for scripts that are linted via the command line. +# + +### Warnings +# Enable or disable warnings based on requirements. +# Use "+WarningName" to display or "-WarningName" to suppress. +# +-no_return_value ## function {0} does not always return a value ++duplicate_formal # duplicate formal argument {0} ++equal_as_assign # test for equality (==) mistyped as assignment (=)?{0} ++var_hides_arg # variable {0} hides argument ++redeclared_var # redeclaration of {0} {1} +-anon_no_return_value ## anonymous function does not always return a value ++missing_semicolon # missing semicolon ++meaningless_block # meaningless block; curly braces have no impact +-comma_separated_stmts ## multiple statements separated by commas (use semicolons?) +-unreachable_code ## unreachable code ++missing_break # missing break statement ++missing_break_for_last_case # missing break statement for last case in switch ++comparison_type_conv # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==) +-inc_dec_within_stmt ## increment (++) and decrement (--) operators used as part of greater statement ++useless_void # use of the void type may be unnecessary (void is always undefined) ++multiple_plus_minus # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs ++use_of_label # use of label +-block_without_braces ## block statement without curly braces ++leading_decimal_point # leading decimal point may indicate a number or an object member ++trailing_decimal_point # trailing decimal point may indicate a number or an object member ++octal_number # leading zeros make an octal number ++nested_comment # nested comment ++misplaced_regex # regular expressions should be preceded by a left parenthesis, assignment, colon, or comma +-ambiguous_newline ## unexpected end of line; it is ambiguous whether these lines are part of the same statement ++empty_statement # empty statement or extra semicolon +-missing_option_explicit ## the "option explicit" control comment is missing ++partial_option_explicit # the "option explicit" control comment, if used, must be in the first script tag ++dup_option_explicit # duplicate "option explicit" control comment ++useless_assign # useless assignment ++ambiguous_nested_stmt # block statements containing block statements should use curly braces to resolve ambiguity ++ambiguous_else_stmt # the else statement could be matched with one of multiple if statements (use curly braces to indicate intent) +-missing_default_case ## missing default case in switch statement ++duplicate_case_in_switch # duplicate case in switch statements ++default_not_at_end # the default case is not at the end of the switch statement ++legacy_cc_not_understood # couldn't understand control comment using /*@keyword@*/ syntax ++jsl_cc_not_understood # couldn't understand control comment using /*jsl:keyword*/ syntax ++useless_comparison # useless comparison; comparing identical expressions ++with_statement # with statement hides undeclared variables; use temporary variable instead ++trailing_comma_in_array # extra comma is not recommended in array initializers ++assign_to_function_call # assignment to a function call ++parseint_missing_radix # parseInt missing radix parameter + +### Output format +# Customize the format of the error message. +# __FILE__ indicates current file path +# __FILENAME__ indicates current file name +# __LINE__ indicates current line +# __ERROR__ indicates error message +# +# Visual Studio syntax (default): ++output-format __FILE__(__LINE__): __ERROR__ +# Alternative syntax: +#+output-format __FILE__:__LINE__: __ERROR__ + + +### Context +# Show the in-line position of the error. +# Use "+context" to display or "-context" to suppress. +# ++context + +### Semicolons +# By default, assignments of an anonymous function to a variable or +# property (such as a function prototype) must be followed by a semicolon. +# ++lambda_assign_requires_semicolon + +### Control Comments +# Both JavaScript Lint and the JScript interpreter confuse each other with the syntax for +# the /*@keyword@*/ control comments and JScript conditional comments. (The latter is +# enabled in JScript with @cc_on@). The /*jsl:keyword*/ syntax is preferred for this reason, +# although legacy control comments are enabled by default for backward compatibility. +# ++legacy_control_comments + + +### JScript Function Extensions +# JScript allows member functions to be defined like this: +# function MyObj() { /*constructor*/ } +# function MyObj.prototype.go() { /*member function*/ } +# +# It also allows events to be attached like this: +# function window::onload() { /*init page*/ } +# +# This is a Microsoft-only JavaScript extension. Enable this setting to allow them. +# +-jscript_function_extensions + + +### Defining identifiers +# By default, "option explicit" is enabled on a per-file basis. +# To enable this for all files, use "+always_use_option_explicit" +-always_use_option_explicit + +# Define certain identifiers of which the lint is not aware. +# (Use this in conjunction with the "undeclared identifier" warning.) +# +# Common uses for webpages might be: ++define exports +#+define document + + +### Files +# Specify which files to lint +# Use "+recurse" to enable recursion (disabled by default). +# To add a set of files, use "+process FileName", "+process Folder\Path\*.js", +# or "+process Folder\Path\*.htm". +# ++process lib/cube/authentication.js ++process lib/cube/bisect.js ++process lib/cube/collectd.js ++process lib/cube/collector.js ++process lib/cube/emitter-http.js ++process lib/cube/emitter-udp.js ++process lib/cube/emitter-ws.js ++process lib/cube/emitter.js ++process lib/cube/endpoint.js ++process lib/cube/evaluator.js ++process lib/cube/event.js ++process lib/cube/index.js ++process lib/cube/metalog.js ++process lib/cube/metric.js ++process lib/cube/models.js ++process lib/cube/reduces.js ++process lib/cube/server.js ++process lib/cube/tiers.js ++process lib/cube/types.js ++process lib/cube/visualizer.js ++process lib/cube/warmer.js + ++process test/*.js ++process bin/*.js diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..baa0031d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - 0.8 diff --git a/Jakefile b/Jakefile new file mode 100644 index 00000000..6e87c501 --- /dev/null +++ b/Jakefile @@ -0,0 +1,38 @@ +var forced_metric_expiration = 1000 * 60 * 60 * 24 * 7, // 7 days + Db = require('./lib/cube/db'); + +namespace("db", function(){ + namespace("metrics", function(){ + desc("Remove metrics with times past the forced metric expiration horizon") + task("remove_expired", [], function(){ + var db = new Db(), + expiration_date = new Date(new Date() - forced_metric_expiration); + + function handle(err) { + if(err) throw err; + } + + db.open(function(err, db){ + handle(err); + var metrics_db = db._dbs.metrics; + metrics_db.collectionNames({namesOnly: true}, function(err, names){ + handle(err); + var metric_names = names.filter(function(name){ return /_metrics$/.test(name); }), + remaining = metric_names.length; + metric_names.forEach(function(name){ + var segments = name.split('.'), + collection_name = (segments.shift(), segments.join('.')); + metrics_db.collection(collection_name, function(err, collection){ + handle(err); + collection.remove({'_id.t': {$lt: expiration_date }}, function(err){ + handle(err); + console.log('Removing ' + collection_name.split('_').join(' ') + ' older than ' + expiration_date); + if(!--remaining) db.close(); + }); + }); + }); + }) + }) + }); + }); +}); diff --git a/Makefile b/Makefile index 759e5661..544ca40c 100644 --- a/Makefile +++ b/Makefile @@ -11,4 +11,4 @@ all: \ lib/cube/metric-expression.js test: all - @$(JS_TESTER) + NODE_ENV=test $(JS_TESTER) --isolate --spec diff --git a/README.md b/README.md index 707e6bb5..f26f6d1b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Cube +[![build status](https://secure.travis-ci.org/infochimps-labs/cube.png)](http://travis-ci.org/infochimps-labs/cube) + **Cube** is a system for collecting timestamped events and deriving metrics. By collecting events rather than metrics, Cube lets you compute aggregate statistics *post hoc*. It also enables richer analysis, such as quantiles and histograms of arbitrary event sets. Cube is built on [MongoDB](http://www.mongodb.org) and available under the [Apache License](/square/cube/blob/master/LICENSE). Want to learn more? [See the wiki.](https://github.com/square/cube/wiki) diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..f35974b6 --- /dev/null +++ b/TODO.md @@ -0,0 +1,270 @@ + + +## Scaling Cube and Mongo + + +With some modifications, you can make cube scale to thousands of inserts per second. + +Cube saves two kinds of records: +* Raw individual events. An event stream of 2400 records/s at 1kB each is 200 GB/day, 1.4 TB/week. +* Metrics aggregated over those events. For an event stream of any size, storing a year's worth of aggregates for 100 different metrics is 25 GB. (That's 10-second, 1-minute, 5-minute and hour aggregates, 68 bytes each) + +For an event stream of 2400 records/s @ 1kB/record, + +* network throughput =~ 2.5 MB/s +* disk write =~ 2.5 MB/s +* mongo inserts =~ 2400 insert/s +* queries =~ (100 metrics, re-calculated 5x each) + +* mongo network in =~ 2.5 MB/s +* mongo network out =~ k + +* mongo vsize +* faults +* mongo db locked +* connections + + insert query update delete getmore command flushes mapped vsize res faults locked db idx miss % qr|qw ar|aw netIn netOut conn time + 0 0 0 0 0 1 0 464m 3.33g 45m 0 local:0.0% 0 0|0 0|0 62b 1k 1 17:58:22 + 0 0 0 0 0 1 0 464m 3.33g 45m 0 local:0.0% 0 0|0 0|0 62b 1k 1 17:58:23 + + inserts - # of inserts per second (* means replicated op) + query - # of queries per second + update - # of updates per second + delete - # of deletes per second + getmore - # of get mores (cursor batch) per second + command - # of commands per second, on a slave its local|replicated + flushes - # of fsync flushes per second + mapped - amount of data mmaped (total data size) megabytes + vsize - virtual size of process in megabytes + res - resident size of process in megabytes + faults - # of pages faults per sec + locked - name of and percent time for most locked database + idx miss - percent of btree page misses (sampled) + qr|qw - queue lengths for clients waiting (read|write) + ar|aw - active clients (read|write) + netIn - network traffic in - bits + netOut - network traffic out - bits + conn - number of open connections + + + +### Database patterns of access + +* Collector: insert on events from receiving event + +* Collector: update on metrics for invalidation (flush) + +* Evaluator: find on metrics for metric calculation + +* Evaluator: find on metrics for pyramidal calculation + +* Evaluator: cursored find on events for calculateFlat + +* Evaluator: save (upsert) on metrics from calculateFlat + + +## Cube do's and don'ts + +* ensure time-based / ascending IDs +* Don't send old metrics +* Turn dates into mongo dates, not text strings +* Short field names + +## Metrics persist, Events expire + +* **Capped events, uncapped metrics** + - modify cube to construct uncapped collections for metrics, and capped collections for events + - change invalidation flusher to delete metrics, not update-with-invalid=true. + +* **Calculation horizon** -- Metrics older than the calculation horizon will not be recalculated from events. + +* **Drop stale events on the floor** + - drop events on the floor if they are older than the 'invalidation' horizon (which is shorter than the calculation horizon) + - Warn any time an invalidation extends more than 5 minutes into the past + +* **Save metrics even if their value is zero** + +### Use connection pool + +### Use Mongo 2.2+, and use separate databases for events and metrics + +### Calculation queue + +Currently, the event flush-er does a range-query update setting "i=false"; under heavy load, this is surprisingly hard on the database. We've observed multiple-second pauses for invalidation; I believe mongo has to hold a write lock open while it does a range query for the metrics to update. Metric updates, by contrast, are a reasonably cheap single-row `save()`. (Side note: we tried uncapping the metrics collection and doing a `remove()`; removes also lock the database, and it was no more efficient.) + +Instead, the collector's flush operation should simply trigger a direct recalculation of the metric. This would halve the number of updates and significantly reduce thevariance of load. + +The challenge is to a) not have everybody in sight run off recalculating metrics; b) not complicate the metric calculation code; c) ensure correctness; d) not introduce coupling. + +proposal a: evaluator contains the queue + +* add an api endpoint to the evaluator: + - endpoints: (method `DELETE`, path `1.0/metric`), and method `GET`, path `1.0/metric/refresh`. (I think this is the best match to HTTP semantics, but if anyone has strong opinions please advise.) + - accepts start and stop; this calls deferred method on `metric.getter` and immediately returns `202 (Accepted)`. +* add code to `metric` to handle request +* make the flusher call the evaluator rather than issue an update. + +Good: simple; bad: requires the evaluator be running to handle invalidations. + +proposal b: database contains the queue + +* add a collection to track calculation requests. rather than updating a range of metrics, flusher inserts a single entry specifying a type + time range. +* add code to coalesce all pending calculation requests for a given type and update the metrics appropriately. + + + +## Scaling Mongo + + + +* More ram helps + +* enable `--directoryperdb` -- with the separate +* (??hypothetical enable `noprealloc=true`. Collections that have heavy write loads will fill up very quickly, and cap from there.) +* enable `cpu=true` +* enable `logappend=true` + +* do not enable `smallfiles`. + +logpath=/ephemeral/log/mongod.log +dbpath=/data + +--slowms Specifies the threshold (in milliseconds) above which long-running queries will appear in the log, and in the system.profiler collection if profiling is enabled. + +* `--journalCommitInterval` -- How often to group/batch commit (ms). Default 100ms +* `--syncdelay arg` -- The number of seconds between data file flushes/syncs. Default 60 (seconds). + - The MongoDB journal file flushes almost immediately, but data files are flushed lazily to optimize performance. The default is 60 seconds. A 0 setting means never, but is not recommended and should never be used with journaling enabled. On Linux, longer settings will likely be ineffective unless /proc/sys/vm/dirty_expire_centisecs is adjusted also. Generally, set syncdelay to 4x the desired dirty_expire_centiseconds value. If backgroundFlushing.average_ms is relatively large (>= 10,000 milliseconds), consider increasing this setting as well as dirty_expire_centiseconds. Under extremely high write volume situations, a higher syncdelay value may result in more journal files as they cannot be rotated (deleted) as quickly. + + +* *do* use a journal + +* `--rest` -- while in development, enables the REST interface. + +To consider -- + + +* connection should be `{safe: false, connectTimeoutMS=XX}` + +* mongo uses 1 thread per CPU connection. + +### System Tuning + +Consider *enabling swap*. EC2 + +* ulimit -- Set file descriptor limit (`-n`) and user process limit (`-u`) to 4k+ (see etc/limits and ulimit) +* sysctl + - overcommit + - swappiness +* Do not use large/huge virtual memory pages (the default is small pages which is what you want) +* Use dmesg to see if box is behaving strangely +* Ensure that readahead settings for the block devices that store the dbpath are acceptable Readahead +* `/proc/sys/vm/dirty_expire_centisecs` + +* cube processes + - run with max memory size ulimit (`-m`) set to a finite value + - (??hypothetical -- run other processes with NICE set so that mongo doesn't become target of OOM killer) + + +## Diagnosis + + +* `backgroundFlushing.average_ms` -- if larger than 10_000 ms adjust mongo's `syncdelay` and the `dirty_expire_centiseconds` sysctl. +* virtualbytes - mappedbytes should be small +* (??hypothetical run `db.runCommand({compact:'collectionname'})` to compact?) +* `mongod --repair` -- will put a heavy load on your DB + - you may see some 'yield' warnings when doing a repair; they're harmless + +* Dump stats on all databases and collections: + + db.foo.stats() + var mongo_stats = {} ; db._adminCommand("listDatabases").databases.forEach(function (d) {mdb = db.getSiblingDB(d.name); mongo_stats[d.name] = {} ; mongo_stats[d.name]["_stats"] = mdb.stats(); mdb.getCollectionNames().forEach(function(c) {mongo_stats[d.name][mdb[c].name] = mdb[c].stats(); })}); printjson(mongo_stats); + +* [Database profiling](http://www.mongodb.org/display/DOCS/Database+Profiler) + + db.getProfilingLevel() + db.setProfilingLevel(level) 0=off 1=record slow queries 2= record all queries + mongod --profile=1 --slowms=15 + + # view queries against collection test.foo: + db.system.profile.find( { info: /test.foo/ } ) + # newest info first: + db.system.profile.find().sort({$natural:-1}) + + - `nscanned` is much higher than nreturned, the database is scanning many objects to find the target objects. Consider creating an index to improve this. + - `reslen` A large number of bytes returned (hundreds of kilobytes or more) causes slow performance. Consider passing `find()` a second parameter of the member names you require. + - `fastmodinsert` -- better than `upsert` + - `moved` -- Indicates the update moved the object on disk. bad. + - `key updates` -- had to reindex. bad. + +* [diagnostic console](http://localhost:28017/) + - start with `--rest` -- while in development, enables the REST interface. + + + + +## Storage + + +### Replica Sets + +* Ensure you are using NTP to minimize clock skew. + +#### Sharding + +Note that you **cannot shard** with cube: capped collections do not work in a sharded database. Replica sets are fine; see above. + +## Indexes + +* "Exact values first. Sorted fields next. Ranged fields after that." See [dex](http://blog.mongolab.com/2012/06/introducing-dex-the-index-bot/). +* use hints to force indexes + +For Capped Collections, natural order is guaranteed to be the insertion order, making this a very efficient way to store and retrieve data in insertion order (much faster than say, indexing on a timestamp field). In addition to forward natural order, items may be retrieved in reverse natural order. For example, to return the 50 most recently inserted items (ordered most recent to less recent) from a capped collection, you would invoke: `c=db.cappedCollection.find().sort({$natural:-1}).limit(50)` + +### Storage + +* Use xfs, mounted with `"noatime"`, or ext4 file system, mounted with "noatime,data=writeback,nobarrier" + +* use separate disks for journals, events and metrics +* use the `--directoryperdb` +* use an SSD for metrics. + - don't need to use an SSD for events. +* commodity drives are OK. + +db.runCommand("journalLatencyTest") +You can run this command on an idle system to get a baseline sync time for journaling. In addition, it is safe to run this command on a busy system to see the sync time on a busy system (which may be higher if the journal directory is on the same volume as the data files). + +* see [Baking up MongoDB](http://www.mongodb.org/display/DOCS/Backups) + +### Amazon EC2 scaling + + m1.large 232 7.68 .32 13 23 7.5 4 2 2 850 2 64 500 High + m1.xlarge 465 15.36 .64 13 23 15 8 4 2 1690 4 64 1000 High + m2.xlarge 327 10.80 .45 14 38 17.1 6.5 2 3.25 420 1 64 Moderate + m2.2xlarge 653 21.60 .90 14 38 34.2 13 4 3.25 850 2 64 High + m2.4xlarge 1307 43.20 1.80 14 38 68.4 26 8 3.25 1690 2 64 1000 High + hi1.4xlarge 2265 74.40 3.10 11 20 60.5 35 16 2.2 2048 ssd 2 64 10GB + +* faster cores is better than more cores+more total CPU. + - However I don't think you're CPU-bound so the m1.xlarge with provisioned EBS probably beats the m2.xlarge. + +* Provisioned IOPS for Amazon EBS + +* 8 drives in a [RAID-10](http://en.wikipedia.org/wiki/Nested_RAID_levels#RAID_1.2B0) configuration + - gives you 4x the storage of any one drive + + $ for drive in /dev/md0 /dev/xvdh{1,2,3,4} ; do sudo blockdev --setra 128 $drive ; done + +* Put the oplog on the local drives + - If you put the journal on the local drives, it will not be present if the machine dies. It will persist through a reboot. Still, it may make sense if you are using a replica set. +* do not put anything on the root partition. + +EBS volumes are just fine. You have plenty of network throughput. + +### Diagnostics + +* disk IO: `iostat -txm 5` +* mongostat: `mongostat --host $(hostname)` +* system: `htop` +* network: `ifstat 5` + diff --git a/bin/collector-config.js b/bin/collector-config.js index 01acae60..da59e974 100644 --- a/bin/collector-config.js +++ b/bin/collector-config.js @@ -1,10 +1,50 @@ -// Default configuration for development. module.exports = { - "mongo-host": "127.0.0.1", - "mongo-port": 27017, - "mongo-database": "cube_development", - "mongo-username": null, - "mongo-password": null, - "http-port": 1080, - "udp-port": 1180 + mongodb: { + 'mongo-host': '127.0.0.1', + 'mongo-port': 27017, + 'mongo-database': 'cube', + 'mongo-username': null, + 'mongo-password': null, + 'mongo-server-options': { + auto_reconnect: true, + poolSize: 8, + socketOptions: { + noDelay: true + } + }, + + 'mongo-metrics': { + autoIndexId: true, + capped: false, + safe: false + }, + + 'mongo-events': { + autoIndexId: true, + capped: true, + size: 1e9, + safe: false + }, + + 'separate-events-database': true, + + 'authentication-collection': 'users' + }, + horizons: { + 'calculation': 1000 * 60 * 60 * 2, // 2 hours + 'invalidation': 1000 * 60 * 60 * 1, // 1 hour + 'forced_metric_expiration': 1000 * 60 * 60 * 24 * 7, // 7 days + }, + 'collectd-mappings': { + 'snmp': { + 'if_octets': 'interface', + 'disk_octets': 'disk', + 'swap_io': 'swap', + 'swap': 'swap' + } + }, + + 'http-port': 1080, + 'udp-port': 1180, + 'authenticator': 'allow_all' }; diff --git a/bin/collector.js b/bin/collector.js index a2b3e80d..62a9a994 100644 --- a/bin/collector.js +++ b/bin/collector.js @@ -1,9 +1,8 @@ -var options = require("./collector-config"), - cube = require("../"), - server = cube.server(options); +'use strict'; -server.register = function(db, endpoints) { - cube.collector.register(db, endpoints); -}; +var cube = require("../"), + server = cube.server(require('./collector-config')); -server.start(); +server + .use(cube.collector.register) + .start(); diff --git a/bin/evaluator-config.js b/bin/evaluator-config.js index 450589da..29e83bc0 100644 --- a/bin/evaluator-config.js +++ b/bin/evaluator-config.js @@ -1,9 +1,49 @@ -// Default configuration for development. module.exports = { - "mongo-host": "127.0.0.1", - "mongo-port": 27017, - "mongo-database": "cube_development", - "mongo-username": null, - "mongo-password": null, - "http-port": 1081 + mongodb: { + 'mongo-host': '127.0.0.1', + 'mongo-port': 27017, + 'mongo-database': 'cube', + 'mongo-username': null, + 'mongo-password': null, + 'mongo-server-options': { + auto_reconnect: true, + poolSize: 8, + socketOptions: { + noDelay: true + } + }, + + 'mongo-metrics': { + autoIndexId: true, + capped: false, + safe: false + }, + + 'mongo-events': { + autoIndexId: true, + capped: true, + size: 1e9, + safe: false + }, + + 'separate-events-database': true, + + 'authentication-collection': 'users' + }, + horizons: { + 'calculation': 1000 * 60 * 60 * 2, // 2 hours + 'invalidation': 1000 * 60 * 60 * 1, // 1 hour + 'forced_metric_expiration': 1000 * 60 * 60 * 24 * 7, // 7 days + }, + 'collectd-mappings': { + 'snmp': { + 'if_octets': 'interface', + 'disk_octets': 'disk', + 'swap_io': 'swap', + 'swap': 'swap' + } + }, + + 'http-port': 1081, + 'authenticator': 'allow_all' }; diff --git a/bin/evaluator.js b/bin/evaluator.js index b5da8894..97038d1f 100644 --- a/bin/evaluator.js +++ b/bin/evaluator.js @@ -1,9 +1,9 @@ -var options = require("./evaluator-config"), - cube = require("../"), - server = cube.server(options); +'use strict'; -server.register = function(db, endpoints) { - cube.evaluator.register(db, endpoints); -}; +var cube = require("../"), + server = cube.server(require('./evaluator-config')); -server.start(); +server + .use(cube.evaluator.register) + .use(cube.visualizer.register) + .start(); diff --git a/bin/warmer-config.js b/bin/warmer-config.js new file mode 100644 index 00000000..cf7e3906 --- /dev/null +++ b/bin/warmer-config.js @@ -0,0 +1,51 @@ +module.exports = { + mongodb: { + 'mongo-host': '127.0.0.1', + 'mongo-port': 27017, + 'mongo-database': 'cube', + 'mongo-username': null, + 'mongo-password': null, + 'mongo-server-options': { + auto_reconnect: true, + poolSize: 8, + socketOptions: { + noDelay: true + } + }, + + 'mongo-metrics': { + autoIndexId: true, + capped: false, + safe: false + }, + + 'mongo-events': { + autoIndexId: true, + capped: true, + size: 1e9, + safe: false + }, + + 'separate-events-database': true, + + 'authentication-collection': 'users' + }, + horizons: { + 'calculation': 1000 * 60 * 60 * 2, // 2 hours + 'invalidation': 1000 * 60 * 60 * 1, // 1 hour + 'forced_metric_expiration': 1000 * 60 * 60 * 24 * 7, // 7 days + }, + 'collectd-mappings': { + 'snmp': { + 'if_octets': 'interface', + 'disk_octets': 'disk', + 'swap_io': 'swap', + 'swap': 'swap' + } + }, + + warmer: { + 'warmer-interval': 1000 * 30, + 'warmer-tier': 1000 * 10 + } +}; diff --git a/bin/warmer.js b/bin/warmer.js new file mode 100644 index 00000000..3e9beddd --- /dev/null +++ b/bin/warmer.js @@ -0,0 +1,6 @@ +'use strict'; + +var cube = require("../"), + warmer = cube.warmer(require('./warmer-config')); + +warmer.start(); diff --git a/examples/helper.js b/examples/helper.js new file mode 100644 index 00000000..a18b1881 --- /dev/null +++ b/examples/helper.js @@ -0,0 +1,60 @@ +'use strict'; +process.env.TZ = 'UTC'; + +var cube = require("../"), + metalog = cube.metalog, + cromulator = require("./random-emitter/cromulator"), + un = cromulator.un, + event_mod = require("../lib/cube/event"), + models = require("../lib/cube/models"), Event = models.Event; + +var options = { + 'mongo-host': '127.0.0.1', + 'mongo-port': 27017, + 'mongo-server-options': { + auto_reconnect: true, + poolSize: 8, + socketOptions: { + noDelay: true + } + }, + 'mongo-database': 'cube' + }, + mongodb = require("mongodb"), + mongo = new mongodb.Server(options["mongo-host"], options["mongo-port"], options["mongo-server-options"]), + db = new mongodb.Db(options["mongo-database"], mongo, { native_parser: true }), + putter, getter; + +var type = 'doh'; + +db.open(function(error) { + putter = event_mod.putter(db); + getter = event_mod.getter(db); + + var emitter = cube.emitter("ws://127.0.0.1:1080"); + + var helper = { + emitter: emitter + }; + + // helper.invalidate = function(){ + // helper.emitter.invalidate_range(type, Date.now - (20 * un.min), Date.now - (12 * un.min)); + // } + + metalog.info('emitter', {is: 'starting'}); + + var thenish = 1346629570000; + + var ev = new Event(type, thenish, {value: 3}); + // emitter.send(ev.to_request()); + + for (var ii = 0; ii <= 3; ii++){ + ev = new Event(type, thenish - ii * ii * 40000, {value: ii}); + putter(ev.to_request()); + } + setTimeout(function(){ metalog.inspectify(event_mod.invalidator().tsets()); }, 200); + setTimeout(function(){ event_mod.stop(); }, 1200); + + metalog.info('emitter', {is: 'stopping'}); + helper.emitter.close(); +}); diff --git a/examples/random-emitter/cromulator.js b/examples/random-emitter/cromulator.js new file mode 100644 index 00000000..290c5720 --- /dev/null +++ b/examples/random-emitter/cromulator.js @@ -0,0 +1,71 @@ +var metalog = require("../../lib/cube/metalog"); + +var un = {}; un.sec = 1000; un.min = 60 * un.sec; un.hr = 60 * un.min; + +var cromulator = { + count: 0, + step: un.sec * 1, + start: Date.now(), + jitter: 2 * un.sec, // spread in event times + visit_rate: 10.0, + un: un +}; + +var ev = { + ramp: 0.0, + walk: 0.0, + characters: { + homer: 324, bart: 263, lisa: 203, marge: 142, scratchy: 79, itchy: 79, maggie: 51, mr_burns: 49, + ned_flanders: 39, milhouse: 38, skinner: 37, sideshow_mel: 31, willie: 30, quimby: 25, moe: 25, + krusty: 24, nelson: 23, wiggum: 22, grampa: 22, frink: 19, apu: 16, sideshow_bob: 14, + selma: 14, patty: 14, barney: 13, mrs_krabappel: 12, comic_book_guy: 12, martin: 10, + dr_hibbert: 10, smithers: 9, ralph: 9, rev_lovejoy: 8, lionel_hutz: 8, fat_tony: 8, chalmers: 8, + snake: 7, otto: 6, dr_nick: 6, cletus: 5, troy_mcclure: 4, todd: 3, rodd: 3, kent_brockman: 3 }, + characters_pool: [] +}; +for (var ch in ev.characters) { for (i=0; i < ev.characters[ch]; i++){ ev.characters_pool.push(ch) } }; + +// random element from a list +function rand_element(list){ return list[Math.floor(Math.random() * list.length)]; } +// Fuzzy sine wave +function sine(since, period, fuzz){ + return (Math.sin(Math.PI * since / period) + normal(0, fuzz)); +} +// Normally-distributed variate with given average and standard deviation -- http://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform +function normal(avg, stdev){ return avg + (stdev * Math.sqrt(-2 * Math.log(Math.random())) * Math.cos(2 * Math.PI * Math.random())); } +// Exponential variate; 0 < lambda, the rate of events -- http://en.wikipedia.org/wiki/Exponential_distribution#Generating_exponential_variates +function exprand(lambda){ return (- Math.log(Math.random()) / lambda); } +// Geometric variate; 0 < p < 1, the probability an event will happen +function geomrand(p){ return Math.floor(exprand( -Math.log(1-p) )); } + +cromulator.report = function(stage, time){ + var stop = cromulator.stop || Date.now(); + return { is: stage, + start: new Date(cromulator.start), + stop: new Date(stop), + per_sec: (1000.0 / cromulator.step), + qty: (stop - cromulator.start) / cromulator.step, + secs: (stop - cromulator.start) / 1000, + count: cromulator.count, + ago: (Date.now() - time)/1000 }; +}; + +cromulator.data_at = function(time){ + var stop = cromulator.stop || Date.now(); + var data = { + sine_45m: sine(time - cromulator.start, 45 * un.min, 0.2), + sine_5m: sine(time - cromulator.start, 5 * un.min, 0.1), + walk: ev.walk += (Math.random() - 0.5), + ramp: ev.ramp += (Math.random() * 20 * cromulator.step / (stop - cromulator.start)), + visits: exprand( un.sec * cromulator.visit_rate / cromulator.step ), + who: rand_element(ev.characters_pool), + people: [rand_element(ev.characters_pool), rand_element(ev.characters_pool), rand_element(ev.characters_pool)], + _meta: { time: time, stop: stop, start: cromulator.start, step: cromulator.step } + }; + ++cromulator.count; + return data; +}; + +cromulator.spread_time = function(time){ return new Date(time + (cromulator.jitter * (Math.random()-0.5)) ) }; + +module.exports = cromulator; diff --git a/examples/random-emitter/random-emitter.js b/examples/random-emitter/random-emitter.js index a9ce70f9..74aa6524 100644 --- a/examples/random-emitter/random-emitter.js +++ b/examples/random-emitter/random-emitter.js @@ -2,7 +2,8 @@ process.env.TZ = 'UTC'; var util = require("util"), cube = require("../../"), // replace with require("cube") - options = require("./random-config"); + options = require("./random-config"), + setImmediate = require("../../set-immediate"); util.log("starting emitter"); var emitter = cube.emitter(options["collector"]); @@ -13,7 +14,14 @@ var start = Date.now() + options["offset"], value = 0, count = 0; -while (start < stop) { +function insert(start){ + if(start > stop){ + util.log("sent " + count + " events"); + util.log("stopping emitter"); + emitter.close(); + return; + } + count++; emitter.send({ type: "random", time: new Date(start), @@ -21,10 +29,7 @@ while (start < stop) { value: value += Math.random() - .5 } }); - start += step; - ++count; + setImmediate(function(){ insert(start + step) }); } -util.log("sent " + count + " events"); -util.log("stopping emitter"); -emitter.close(); +insert(start); diff --git a/examples/random-emitter/random-streamer.js b/examples/random-emitter/random-streamer.js new file mode 100644 index 00000000..841e8f25 --- /dev/null +++ b/examples/random-emitter/random-streamer.js @@ -0,0 +1,50 @@ +process.env.TZ = 'UTC'; + +var cube = require("../../"), // replace with require("cube") + metalog = cube.metalog, + options = require("./random-config"), + cromulator = require("./cromulator"), + Event = require("../../lib/cube/models/event"); + +var options = { + "collector": "ws://127.0.0.1:6000", + + // The offset and duration to backfill, in milliseconds. + // For example, if the offset is minus four hours, then the first event that + // the random emitter sends will be four hours old. It will then generate more + // recent events based on the step interval, all the way up to the duration. + + "event_frequency": 5000, // per second + event_batch: 500, + event_type: "doh" +}; + +var emitter = cube.emitter(options["collector"]), + step = 1000 / options.event_frequency, + batch = options.event_batch || (step < 1 ? (1 / step) : 1); + + +function setup_cromulator(){ + var day = 1000 * 60 * 60 * 24; + cromulator.start = new Date(Math.floor(new Date() / day) * day); + cromulator.stop = new Date(+cromulator.start + day); + cromulator.step = step; + } + +metalog.info('emitter', cromulator.report('starting')); + +function send(){ + if (!cromulator.stop || +cromulator.stop <= +(new Date())) setup_cromulator(); + var i = -1; + + while (++i < batch) { + var time = new Date(), + event = new Event(options.event_type, time, cromulator.data_at(time)); + if (cromulator.count % options.event_frequency == 0) metalog.info('emitter', {em: emitter.report(), cr: cromulator.report('progress', time)}); + event.force = true; + emitter.send(event.to_request()); + } +} + +var interval = 1000 / (options.event_frequency / batch); +setInterval(send, interval); \ No newline at end of file diff --git a/lib/cube/authentication.js b/lib/cube/authentication.js new file mode 100644 index 00000000..4ea28aa2 --- /dev/null +++ b/lib/cube/authentication.js @@ -0,0 +1,115 @@ +'use strict'; + +// +// authentication -- authenticate user identities and authorize their action +// +// Call an authenticator with the request and ok/no callbacks: +// * if authentication succeeds, a permissions record is attached to the request +// object as `request.authorized`, and sent as the callback parameter. +// * if authentication fails, the callback is called with a short string +// describing the reason. +// +// The authenticators include: +// * allow_all -- uniformly authenticates all requests, authorizing all to write +// * read_only -- uniformly authenticates all requests, authorizing none to write +// * mongo_cookie -- validates bcrypt'ed token in cookie with user record in db. +// +// This module does not provide any means for creating user tokens; do this in +// your front-end app. +// + +var mongodb = require("mongodb"), + cookies = require("cookies"), + bcrypt = require("bcrypt"), + metalog = require('./metalog'), + config = require('./config'), + authentication = {}; + +authentication.authenticator = function(strategy, db){ + metalog.minor('cube_auth', { strategy: strategy }); + return authentication[strategy](db); +}; + +authentication.allow_all = function(){ + function check(request, auth_ok, auth_no) { + metalog.event('cube_auth', { authenticator: 'allow_all', path: request.url }, 'minor'); + request.authorized = { admin: true }; + return auth_ok(request.authorized); + } + return { check: check }; +}; + +authentication.read_only = function(){ + function check(request, auth_ok, auth_no) { + metalog.event('cube_auth', { authenticator: 'read_only', path: request.url }, 'minor'); + request.authorized = { admin: false }; + return auth_ok(request.authorized); + } + return { check: check }; +}; + +authentication.mongo_cookie = function(db){ + var users; + + db.collection(config.mongodb["authentication-collection"] || "users", function(error, clxn) { + if (error) throw(error); + // TODO: check if not collection? + users = clxn; + }); + + function check(request, auth_ok, auth_no) { + var cookie = (new cookies(request)).get('_cube_session'), + token = decodeURIComponent(cookie).split('--'), // token[0] = token uid, token[1] = token secret + token_uid = new Buffer(token[0] + '', 'base64').toString('utf8'), + token_secret = new Buffer(token[1] + '', 'base64').toString('utf8'); + + metalog.event('cube_auth', { is: 'hi', tu: token_uid }, 'minor'); + + if (! (cookie && token && token_uid && token_secret)){ + metalog.event('cube_auth', { is: 'no', r: 'no_token_in_request' }); + auth_no('no_token_in_request'); + return; + } + + validate_token({"tokens.uid": token_uid}, auth_ok, auth_no); + + function validate_token(query, auth_ok, auth_no){ + // Asynchronously load the requested user. + users.findOne(query, function(error, user) { + if((!error) && user) { + metalog.event('cube_auth', { is: 'ok', tu: token_uid, u: user._id }, 'minor'); + var auth_info = user.tokens.filter(function(t){ return t.uid === token_uid; }); + + if(auth_info.length === 1){ + bcrypt.compare(token_secret, auth_info[0].hashed_secret, function(err, res) { + if (!err && res === true ){ + delete auth_info[0].hashed_secret; + request.authorized = auth_info[0]; + auth_ok(request.authorized); + } else { + metalog.event('cube_auth', { is: 'no', r: 'bad_token', tu: token_uid, u: user }); + auth_no('bad_token'); + } + }); + } else { + metalog.event('cube_auth', { is: 'no', r: 'missing_token', tu: token_uid, u: user }); + auth_no('missing_token'); + } + } else { + metalog.event('cube_auth', { is: 'no', r: 'missing_user', tu: token_uid }); + auth_no('missing_user'); + } + }); + } + } + return { check: check }; +}; + +// base-64 encode a uid and bcrypted secret +authentication.gen_cookie = function(session_name, uid, secret){ + var encoded_uid = new Buffer(uid, 'utf8').toString('base64'); + var encoded_sec = new Buffer(secret, 'utf8').toString('base64'); + return (session_name+"="+encoded_uid+"--"+encoded_sec+";"); +}; + +module.exports = authentication; diff --git a/lib/cube/bisect.js b/lib/cube/bisect.js index 55fc7c3c..30f3e3de 100644 --- a/lib/cube/bisect.js +++ b/lib/cube/bisect.js @@ -1,3 +1,5 @@ +'use strict'; + module.exports = bisect; function bisect(a, x) { diff --git a/lib/cube/collectd.js b/lib/cube/collectd.js index 1c82717d..23c97661 100644 --- a/lib/cube/collectd.js +++ b/lib/cube/collectd.js @@ -1,3 +1,5 @@ +'use strict'; + exports.putter = function(putter) { var valuesByKey = {}; @@ -47,13 +49,13 @@ exports.putter = function(putter) { } return function(request, response) { - var content = ""; + var content = []; request.on("data", function(chunk) { - content += chunk; + content.push(chunk); }); request.on("end", function() { var future = Date.now() / 1e3 + 1e9; - JSON.parse(content).forEach(function(values) { + JSON.parse(content.join('')).forEach(function(values) { var time = values.time; if (time > future) time /= 1073741824; values.time = Math.round(time) * 1e3; diff --git a/lib/cube/collector.js b/lib/cube/collector.js index f6a1c2fb..37d3918c 100644 --- a/lib/cube/collector.js +++ b/lib/cube/collector.js @@ -1,12 +1,24 @@ -var endpoint = require("./endpoint"); +'use strict'; // +// collector -- listen for incoming metrics +// + +var endpoint = require("./endpoint"), + metalog = require('./metalog'); + var headers = { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }; -exports.register = function(db, endpoints) { +// Register Collector listeners at their appropriate paths: +// +// * putter, handles each isolated event -- see event.js +// * poster, an HTTP listener -- see below +// * collectd, a collectd listener -- see collectd.js +// +exports.register = function collector(db, endpoints) { var putter = require("./event").putter(db), poster = post(putter); @@ -26,16 +38,26 @@ exports.register = function(db, endpoints) { endpoints.udp = putter; }; +// +// Construct HTTP listener +// +// * aggregate content into a complete request +// * JSON-parse the request body +// * dispatch each metric as an event to the putter +// function post(putter) { return function(request, response) { - var content = ""; + var content = []; request.on("data", function(chunk) { - content += chunk; + content.push(chunk); }); request.on("end", function() { try { - JSON.parse(content).forEach(putter); + JSON.parse(content.join('')).forEach(function(event) { + putter(event); + }); } catch (e) { + metalog.event("cube_request", { at: "c", res: "collector_post_error", error: e, code: 400 }); response.writeHead(400, headers); response.end(JSON.stringify({error: e.toString()})); return; @@ -45,3 +67,6 @@ function post(putter) { }); }; } + +// ignore -- used for testing +exports._post = post; diff --git a/lib/cube/config.js b/lib/cube/config.js new file mode 100644 index 00000000..108d03d2 --- /dev/null +++ b/lib/cube/config.js @@ -0,0 +1,13 @@ +'use strict'; + +var metalog = require('./metalog'); +metalog.send_events = false; + +module.exports = { + load: function(options) { + Object.keys(options).forEach(function (key) { + module.exports[key] = options[key]; + }); + return module.exports; + } +} diff --git a/lib/cube/core_ext/model.js b/lib/cube/core_ext/model.js new file mode 100644 index 00000000..825d5f86 --- /dev/null +++ b/lib/cube/core_ext/model.js @@ -0,0 +1,55 @@ +'use strict'; + +var util = require('util'), + _ = require('underscore'); + +function Model(){}; + +function modelize(ctor, super_) { + super_ = super_ || Model; + if(super_ !== ctor) util.inherits(ctor, super_); + + var properties = {}; + + properties['eventize'] = { value: eventize }; + properties['_trace'] = { value: null, enumerable: false, writable: true, configurable: true }; + properties['setProperty'] = { value: function(name, desc){ Object.defineProperty(this, name, desc) }}; + properties['setProperties'] = { + value: function(descs){ + var _this = this; + _.keys(descs).forEach(function(key){ + Object.defineProperty(_this, key, descs[key]); + }) + } + }; + + Object.defineProperties(ctor.prototype, properties); + Object.defineProperty(ctor, 'setProperty', { value: function(name, desc){ Object.defineProperty(ctor.prototype, name, desc); }}); + Object.defineProperty(ctor, 'setProperties', { + value: function(descs){ + var _this = this; + _.keys(descs).forEach(function(key){ + Object.defineProperty(ctor.prototype, key, descs[key]); + }) + } + }); + + return ctor; +}; + +function eventize() { + var emitter = new (require('events').EventEmitter)(), + _this = this; + + Object.keys(Object.getPrototypeOf(emitter)).forEach(function(prop){ + Object.defineProperty(_this, prop, { value: function(args){ + emitter[prop].apply(emitter, arguments); + }}); + }); +} + +modelize(Model); + +Model.modelize = modelize; + +module.exports = Model; \ No newline at end of file diff --git a/lib/cube/database.js b/lib/cube/database.js deleted file mode 100644 index a7de1ad2..00000000 --- a/lib/cube/database.js +++ /dev/null @@ -1,55 +0,0 @@ -var mongodb = require("mongodb"); - -var database = module.exports = {}; - -// Opens MongoDB driver given connection URL and optional options: -// -// { -// "mongo-url": "", -// "mongo-options": { -// "db": { "safe": false }, -// "server": { "auto_reconnect": true }, -// "replSet": { "read_secondary": true } -// } -// } -// -// See http://docs.mongodb.org/manual/reference/connection-string/ for details. -// You can also specify a Replica Set this way. -// -database.open = function(config, callback) { - var url = config["mongo-url"] || database.config2url(config), - options = config["mongo-options"] || database.config2options(config); - return mongodb.Db.connect(url, options, callback); -}; - -// -// For backwards-compatibility you can specify a connection to a single Mongo(s) as follows: -// -// { -// "mongo-host": "localhost", -// "mongo-port": "27017", -// "mongo-server-options": { "auto_reconnect": true }, -// "mongo-database": "cube", -// "mongo-database-options": { "safe": false }, -// "mongo-username": null, -// "mongo-password": null, -// } -// (defaults are shown) -// -database.config2url = function(config) { - var user = config["mongo-username"], - pass = config["mongo-password"], - host = config["mongo-host"] || "localhost", - port = config["mongo-port"] || 27017, - name = config["mongo-database"] || "cube", - auth = user ? user+":"+pass+"@" : ""; - return "mongodb://"+auth+host+":"+port+"/"+name; -}; - -database.config2options = function(config) { - return { - db: config["mongo-database-options"] || { safe: false }, - server: config["mongo-server-options"] || { auto_reconnect: true }, - replSet: { read_secondary: true } - }; -}; \ No newline at end of file diff --git a/lib/cube/db.js b/lib/cube/db.js new file mode 100644 index 00000000..ea100c3e --- /dev/null +++ b/lib/cube/db.js @@ -0,0 +1,253 @@ +'use strict'; + +var util = require("util"), + mongodb = require("mongodb"), + queue = require("queue-async"), + metalog = require("./metalog"), + config = require("./config"), + db_index = 0; + +function mongoUrl(options) { + if ("mongo-url" in options) { + return options["mongo-url"]; + } + + var user = options["mongo-username"], + pass = options["mongo-password"], + host = options["mongo-host"] || "localhost", + port = options["mongo-port"] || 27017, + name = options["mongo-database"] || "cube", + auth = user ? user + ":" + pass + "@" : ""; + return "mongodb://" + auth + host + ":" + port + "/" + name; +} + +function mongoOptions(options) { + return { + db: options["mongo-database-options"] || { native_parser: true, safe: false }, + server: options["mongo-server-options"] || { auto_reconnect: true }, + replSet: { read_secondary: true } + } +} + +function Db(){ + var metric_options, event_options, db_client, events_client, metrics_client; + var db_id = db_index++; + + var db = this; + db.open = open; + db.close = close; + db.isConnected = isConnected; + + var collections = {}; // cache of collection handles + var pending_callbacks = {}; // callbacks waiting for collections to be found + var collection_prefix = ''; + + this.report = function report(){ return { idx: db_id, isHalted: db.isHalted }; }; + + // + // Connect to mongodb. + // + + function open(callback){ + if (db_client) { metalog.minor('mongo_already_open', db.report()); return callback(null, db); } + + var mongoConfig = config.mongodb, + url = mongoUrl(mongoConfig), + options = mongoOptions(mongoConfig); + + collection_prefix = mongoConfig["collection_prefix"] || ''; + metric_options = mongoConfig["mongo-metrics"]; + event_options = mongoConfig["mongo-events"]; + + metalog.info('mongo_connect', db.report()); + + var q = queue(); + + q.defer(mongodb.Db.connect, url, options); + + // Open separate events database if configured + if (mongoConfig["separate-events-database"]) q.defer(mongodb.Db.connect, url + '-events', options); + + // Open separate metrics database if configured + if (mongoConfig["separate-metrics-database"]) q.defer(mongodb.Db.connect, url + '-metrics', options); + + q.awaitAll(function(err, results) { + if (err) return callback(err); + + var connId = 0; + db_client = results[connId++]; + + if (mongoConfig["separate-events-database"]) events_client = results[connId++]; + else events_client = db_client; + + if (mongoConfig["separate-metrics-database"]) metrics_client = results[connId++]; + else metrics_client = db_client; + + Object.defineProperty(db, "_dbs", { + configurable: true, + value: { + main: db_client, + metrics: metrics_client, + events: events_client + } + }); + + db.metrics = metrics_collection_factory(metrics_client); + db.events = events_collection_factory(events_client); + db.types = types(events_client); + db.collection = collection(db_client); + db.clearCache = function(callback){ collections = {}; if(callback) callback(null, db); }; + + callback(null, db); + }); + } + + + // + // Close connection to mongodb. + // + + function close(callback){ + metalog.trace('mongo_closing', db.report()); + if (! db_client) return metalog.minor('mongo_already_closed'); + + db.clearCache(); + delete db.metrics; + delete db.events; + delete db.types; + delete db.collection; + delete db.clearCache; + delete db._clients; + + if (isConnected()){ db_client.close(); } + db_client = events_client = metrics_client = null; + db.isHalted = true; + + if(callback) return callback(null); + } + db.isHalted = false; + + function isConnected(){ + try { + return db_client.serverConfig.isConnected(); + } catch (e){ + return false; + } + } + + function metrics_collection_factory(client){ + return function metrics(name, on_collection, tr){ + var clxnname = (collection_prefix + name + "_metrics"); + return clxn_for(client, clxnname, (metric_options||{safe: false}), on_collection, on_m_create, tr); + function on_m_create(clxn, oc_cb){ + clxn.ensureIndex({"i": 1, "_id.e": 1, "_id.l": 1, "_id.t": 1}, handle); + clxn.ensureIndex({"i": 1, "_id.l": 1, "_id.t": 1}, function(error){ oc_cb(error, clxn); }); + } + }; + } + + function events_collection_factory(client){ + return function events(name, on_collection, tr){ + var clxnname = (collection_prefix + name + "_events"); + return clxn_for(client, clxnname, (event_options||{safe: false}), on_collection, on_e_create, tr); + function on_e_create(clxn, oc_cb){ + db.metrics(name, function also_make_metrics_clxn(error){ + if (error) return oc_cb(error); + clxn.ensureIndex({"t": 1}, function(error){ oc_cb(error, clxn); }); + }); + } + }; + } + + function clxn_for(client, clxnname, clxnopts, on_collection, on_create, tr){ + on_collection = on_collection || function(){}; + + // If we've cached the collection, call back immediately + if (collections[clxnname]) return on_collection(null, collections[clxnname]); + + // If someone is already creating the collection for this new type, + // then append the callback to the queue for later save. + if (clxnname in pending_callbacks){ + metalog.trace('clxnQ', tr, {clxnname: clxnname, id: db_id}); + return pending_callbacks[clxnname].push(on_collection); + } + + // Otherwise, it's up to us to create the corresponding collection, then save + // any requests that have queued up in the interim! + + // First add the new event to the queue. + pending_callbacks[clxnname] = [on_collection]; + + // Create collection, and issue callback for index creation, etc + client.createCollection(clxnname, clxnopts, function(error, clxn){ + metalog.trace('clxnC', tr); + if (error && (error.errmsg == "collection already exists")){ + metalog.trace('clxnE', tr); + return client.collection(clxnname, adopt_collection); + } + if (!error) return on_create(clxn, adopt_collection); + if (error) return adopt_collection(error); + }); + + function adopt_collection(error, clxn){ + var callbacks = pending_callbacks[clxnname]; + delete pending_callbacks[clxnname]; + + metalog.trace('clxnD', tr); + if (! error){ + clxn = logging_clxn(clxn, clxnname); + collections[clxnname] = clxn; + } + callbacks.forEach(function(cb){ + try{ cb(error, clxn); } + catch(error){ metalog.error('db_pending_callbacks', error); } + }); + } + } + + function collection(client){ + return function(name, callback){ + if (collections[name]) return callback(null, collections[name]); + + client.collection(name, function(error, collection){ + if(!error) collections[name] = collection; + callback(error, collection); + }); + } + } + + function logging_clxn(collection, type){ + var orig_find = collection.find; + collection.find = function(){ + //metalog.minor((type + '_find'), {query: arguments}); + return orig_find.apply(this, arguments); + }; + return collection; + } + + var eventRe = /_events$/; + + function types(client) { + return function(request, callback) { + client.collectionNames(function(error, names) { + handle(error); + callback(names + .map(function(d) { return d.name.split(".")[1]; }) + .filter(function(d) { return eventRe.test(d); }) + .map(function(d) { return d.substring(0, d.length - 7); }) + .sort()); + }); + }; + }; + +} + +function handle(error){ + if (!error) return; + metalog.error('db error', error); + throw error; +} + +function noop(){}; + +module.exports = Db; diff --git a/lib/cube/emitter-http.js b/lib/cube/emitter-http.js index 0fecc6a9..2ddb1868 100644 --- a/lib/cube/emitter-http.js +++ b/lib/cube/emitter-http.js @@ -1,3 +1,5 @@ +'use strict'; + var util = require("util"), http = require("http"); diff --git a/lib/cube/emitter-udp.js b/lib/cube/emitter-udp.js index db9bf8fc..119af507 100644 --- a/lib/cube/emitter-udp.js +++ b/lib/cube/emitter-udp.js @@ -1,3 +1,5 @@ +'use strict'; + var util = require("util"), dgram = require("dgram"), setImmediate = require("./set-immediate"); diff --git a/lib/cube/emitter-ws.js b/lib/cube/emitter-ws.js index fbd6c600..0e371d27 100644 --- a/lib/cube/emitter-ws.js +++ b/lib/cube/emitter-ws.js @@ -1,18 +1,21 @@ -var util = require("util"), - websocket = require("websocket"); +'use strict'; + +var util = require("util"), + websocket = require("websocket"), + metalog = require("./metalog"); // returns an emitter which sends events one at a time to the given ws://host:port module.exports = function(protocol, host, port) { var emitter = {}, queue = [], - url = protocol + "//" + host + ":" + port + "/1.0/event/put", + url = protocol + "\/\/" + host + ":" + port + "/1.0/event/put", socket, timeout, closing; function close() { + metalog.warn('cube_emitter', {is: 'closing socket', emitter: emitter.report()}); if (socket) { - util.log("closing socket"); socket.removeListener("error", reopen); socket.removeListener("close", reopen); socket.close(); @@ -23,6 +26,7 @@ module.exports = function(protocol, host, port) { function closeWhenDone() { closing = true; if (socket) { + metalog.warn('cube_emitter', {is: 'closing when done', emitter: emitter.report()}); if (!socket.bytesWaitingToFlush) close(); else setTimeout(closeWhenDone, 1000); } @@ -31,7 +35,7 @@ module.exports = function(protocol, host, port) { function open() { timeout = 0; close(); - util.log("opening socket: " + url); + metalog.warn('cube_emitter', {is: 'opening socket', url: url}); var client = new websocket.client(); client.on("connect", function(connection) { socket = connection; @@ -48,7 +52,7 @@ module.exports = function(protocol, host, port) { function reopen() { if (!timeout && !closing) { - util.log("reopening soon"); + metalog.warn('cube_emitter', {is: 'reopening soon', delay: 1000}); timeout = setTimeout(open, 1000); } } @@ -58,8 +62,8 @@ module.exports = function(protocol, host, port) { while (event = queue.pop()) { try { socket.sendUTF(JSON.stringify(event)); - } catch (e) { - util.log(e.stack); + } catch (err) { + metalog.warn('cube_emitter', {is: 'error', error: err.message, stack: er.stack}); reopen(); return queue.push(event); } @@ -67,10 +71,15 @@ module.exports = function(protocol, host, port) { } function log(message) { - util.log(message.utf8Data); + metalog.minor('cube_emitter', {is: 'response', message: message.utf8Data, emitter: emitter.report()}); + } + + emitter.report = function report(){ + return {pending: (socket && socket.bytesWaitingToFlush), socket: (socket ? 'open' : 'closed'), url: url}; } emitter.send = function(event) { + metalog.trace('emitter_send', event); queue.push(event); if (socket) flush(); return emitter; diff --git a/lib/cube/emitter.js b/lib/cube/emitter.js index d48f6ba3..c465796e 100644 --- a/lib/cube/emitter.js +++ b/lib/cube/emitter.js @@ -1,4 +1,13 @@ -var url = require("url"), +'use strict'; + +// +// emitter - writes events to the collector. +// +// URL's scheme determines UDP, websocket or HTTP emitter, as appropriate. +// + +var util = require("util"), + url = require("url"), http = require("./emitter-http"), udp = require("./emitter-udp"), ws = require("./emitter-ws"); diff --git a/lib/cube/endpoint.js b/lib/cube/endpoint.js index 66e0c9ed..98490a02 100644 --- a/lib/cube/endpoint.js +++ b/lib/cube/endpoint.js @@ -1,12 +1,24 @@ -// creates an endpoint with given HTTP method, URL path and dispatch (function) -// (method argument is optional) -// endpoints are evaluated in server.js and -// dispatch(request, response) is called if path/method matches +'use strict'; + +// +// endpoint -- router for requests. +// +// specify +// * optional HTTP method ("GET", "POST", etc) +// * path, a String or RegExp +// * dispatch, a callback to invoke +// + module.exports = function(method, path, dispatch) { - return { - match: arguments.length < 3 - ? (dispatch = path, path = method, function(p) { return p == path; }) - : function(p, m) { return m == method && p == path; }, - dispatch: dispatch - }; -} + var match; + if (method instanceof RegExp) { + dispatch = path, path = method; + match = function(p, m) { return path.test(p); }; + } else if (arguments.length < 3) { + dispatch = path, path = method; + match = function(p) { return p == path; }; + } else { // path is a string + match = function(p, m) { return m == method && p == path; }; + } + return { match: match, dispatch: dispatch }; +}; diff --git a/lib/cube/evaluator.js b/lib/cube/evaluator.js index 20e911d5..3bd364a4 100644 --- a/lib/cube/evaluator.js +++ b/lib/cube/evaluator.js @@ -1,4 +1,7 @@ +'use strict'; + var endpoint = require("./endpoint"), + metalog = require("./metalog"), url = require("url"); // To avoid running out of memory, the GET endpoints have a maximum number of @@ -13,9 +16,10 @@ var headers = { }; exports.register = function(db, endpoints) { - var event = require("./event").getter(db), - metric = require("./metric").getter(db), - types = require("./types").getter(db); + var event = require("./event").getter(db), + event_putter = require("./event").putter(db), + metric = require("./metric").getter(db), + types = db.types; // endpoints.ws.push( @@ -26,14 +30,37 @@ exports.register = function(db, endpoints) { // endpoints.http.push( - endpoint("GET", "/1.0/event", eventGet), - endpoint("GET", "/1.0/event/get", eventGet), - endpoint("GET", "/1.0/metric", metricGet), - endpoint("GET", "/1.0/metric/get", metricGet), - endpoint("GET", "/1.0/types", typesGet), - endpoint("GET", "/1.0/types/get", typesGet) + endpoint("GET", "/1.0/event", eventGet), + endpoint("GET", "/1.0/event/get", eventGet), + endpoint("GET", "/1.0/metric", metricGet), + endpoint("GET", "/1.0/metric/get", metricGet), + endpoint("DELETE", "/1.0/metric", metricRefresh), + endpoint("GET", "/1.0/metric/refresh", metricRefresh), + endpoint("GET", "/1.0/types", typesGet), + endpoint("GET", "/1.0/types/get", typesGet) ); + function metricRefresh(request, response) { + request = url.parse(request.url, true).query; + + if (! (request.start && request.stop && request.type)) { + resp = {error: 'specify type, start and stop'}; + metalog.info('evaluator_refresh', resp); + response.writeHead(400, headers); + response.end('{}'); + } else { + var type = request.type, + start = new Date(request.start), + stop = new Date(request.stop); + + metalog.info('evaluator_refresh', {type: type, start: start, stop: stop}); + event_putter.invalidate_range(type, start, stop); + + response.writeHead(202, headers); + response.end('{}'); + } + } + function eventGet(request, response) { request = url.parse(request.url, true).query; @@ -53,7 +80,7 @@ exports.register = function(db, endpoints) { } function callback(d) { - if (d == null) response.end(JSON.stringify(data.reverse())); + if (d === null) response.end(JSON.stringify(data.reverse())); else data.push(d); } } diff --git a/lib/cube/event-expression.js b/lib/cube/event-expression.js index baaf39e7..c4950288 100644 --- a/lib/cube/event-expression.js +++ b/lib/cube/event-expression.js @@ -1,5 +1,32 @@ module.exports = (function(){ - /* Generated by PEG.js 0.6.2 (http://pegjs.majda.cz/). */ + /* + * Generated by PEG.js 0.7.0. + * + * http://pegjs.majda.cz/ + */ + + function quote(s) { + /* + * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a + * string literal except for the closing quote character, backslash, + * carriage return, line separator, paragraph separator, and line feed. + * Any character may appear in the form of an escape sequence. + * + * For portability, we also escape escape all control and non-ASCII + * characters. Note that "\0" and "\v" escape sequences are not used + * because JSHint does not like the first and IE the second. + */ + return '"' + s + .replace(/\\/g, '\\\\') // backslash + .replace(/"/g, '\\"') // closing quote character + .replace(/\x08/g, '\\b') // backspace + .replace(/\t/g, '\\t') // horizontal tab + .replace(/\n/g, '\\n') // line feed + .replace(/\f/g, '\\f') // form feed + .replace(/\r/g, '\\r') // carriage return + .replace(/[\x00-\x07\x0B\x0E-\x1F\x80-\uFFFF]/g, escape) + + '"'; + } var result = { /* @@ -10,36 +37,36 @@ module.exports = (function(){ */ parse: function(input, startRule) { var parseFunctions = { - "_": parse__, - "array_literal": parse_array_literal, - "character_escape_sequence": parse_character_escape_sequence, - "digit": parse_digit, - "digit19": parse_digit19, - "digits": parse_digits, - "double_string_char": parse_double_string_char, - "e": parse_e, - "escape_character": parse_escape_character, - "escape_sequence": parse_escape_sequence, + "start": parse_start, "event_expression": parse_event_expression, "event_filter_expression": parse_event_filter_expression, - "event_member_expression": parse_event_member_expression, "event_value_expression": parse_event_value_expression, - "exp": parse_exp, + "event_member_expression": parse_event_member_expression, "filter_operator": parse_filter_operator, - "frac": parse_frac, - "hex_digit": parse_hex_digit, - "hex_escape_sequence": parse_hex_escape_sequence, + "type": parse_type, "identifier": parse_identifier, - "int": parse_int, "literal": parse_literal, - "non_escape_character": parse_non_escape_character, - "number": parse_number, - "single_escape_character": parse_single_escape_character, - "single_string_char": parse_single_string_char, - "start": parse_start, + "array_literal": parse_array_literal, "string": parse_string, - "type": parse_type, + "double_string_char": parse_double_string_char, + "single_string_char": parse_single_string_char, + "escape_sequence": parse_escape_sequence, + "character_escape_sequence": parse_character_escape_sequence, + "single_escape_character": parse_single_escape_character, + "non_escape_character": parse_non_escape_character, + "escape_character": parse_escape_character, + "hex_escape_sequence": parse_hex_escape_sequence, "unicode_escape_sequence": parse_unicode_escape_sequence, + "number": parse_number, + "int": parse_int, + "frac": parse_frac, + "exp": parse_exp, + "digits": parse_digits, + "e": parse_e, + "digit": parse_digit, + "digit19": parse_digit19, + "hex_digit": parse_hex_digit, + "_": parse__, "whitespace": parse_whitespace }; @@ -52,10 +79,9 @@ module.exports = (function(){ } var pos = 0; - var reportMatchFailures = true; - var rightmostMatchFailuresPos = 0; - var rightmostMatchFailuresExpected = []; - var cache = {}; + var reportFailures = 0; + var rightmostFailuresPos = 0; + var rightmostFailuresExpected = []; function padLeft(input, padding, length) { var result = input; @@ -70,2830 +96,2136 @@ module.exports = (function(){ function escape(ch) { var charCode = ch.charCodeAt(0); + var escapeChar; + var length; if (charCode <= 0xFF) { - var escapeChar = 'x'; - var length = 2; + escapeChar = 'x'; + length = 2; } else { - var escapeChar = 'u'; - var length = 4; + escapeChar = 'u'; + length = 4; } return '\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), '0', length); } - function quote(s) { - /* - * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a - * string literal except for the closing quote character, backslash, - * carriage return, line separator, paragraph separator, and line feed. - * Any character may appear in the form of an escape sequence. - */ - return '"' + s - .replace(/\\/g, '\\\\') // backslash - .replace(/"/g, '\\"') // closing quote character - .replace(/\r/g, '\\r') // carriage return - .replace(/\n/g, '\\n') // line feed - .replace(/[\x80-\uFFFF]/g, escape) // non-ASCII characters - + '"'; - } - function matchFailed(failure) { - if (pos < rightmostMatchFailuresPos) { + if (pos < rightmostFailuresPos) { return; } - if (pos > rightmostMatchFailuresPos) { - rightmostMatchFailuresPos = pos; - rightmostMatchFailuresExpected = []; + if (pos > rightmostFailuresPos) { + rightmostFailuresPos = pos; + rightmostFailuresExpected = []; } - rightmostMatchFailuresExpected.push(failure); + rightmostFailuresExpected.push(failure); } function parse_start() { - var cacheKey = 'start@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse__(); - if (result3 !== null) { - var result4 = parse_event_expression(); - if (result4 !== null) { - var result5 = parse__(); - if (result5 !== null) { - var result1 = [result3, result4, result5]; + var result0, result1, result2; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse__(); + if (result0 !== null) { + result1 = parse_event_expression(); + if (result1 !== null) { + result2 = parse__(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(expression) { expression.source = input; return expression; })(result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, expression) { expression.source = input; return expression; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_event_expression() { - var cacheKey = 'event_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_event_value_expression(); - if (result3 !== null) { - var result4 = []; - var savedPos2 = pos; - var result6 = parse__(); - if (result6 !== null) { - if (input.substr(pos, 1) === ".") { - var result7 = "."; - pos += 1; + var result0, result1, result2, result3, result4, result5; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + result0 = parse_event_value_expression(); + if (result0 !== null) { + result1 = []; + pos2 = pos; + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 46) { + result3 = "."; + pos++; } else { - var result7 = null; - if (reportMatchFailures) { + result3 = null; + if (reportFailures === 0) { matchFailed("\".\""); } } - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - var result9 = parse_event_filter_expression(); - if (result9 !== null) { - var result5 = [result6, result7, result8, result9]; + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_event_filter_expression(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; - } - while (result5 !== null) { - result4.push(result5); - var savedPos2 = pos; - var result6 = parse__(); - if (result6 !== null) { - if (input.substr(pos, 1) === ".") { - var result7 = "."; - pos += 1; + result2 = null; + pos = pos2; + } + while (result2 !== null) { + result1.push(result2); + pos2 = pos; + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 46) { + result3 = "."; + pos++; } else { - var result7 = null; - if (reportMatchFailures) { + result3 = null; + if (reportFailures === 0) { matchFailed("\".\""); } } - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - var result9 = parse_event_filter_expression(); - if (result9 !== null) { - var result5 = [result6, result7, result8, result9]; + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_event_filter_expression(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(value, filters) { - value.filter = function(filter) { - var i = -1, n = filters.length; - while (++i < n) filters[i][3](filter); - value.exists(filter); - }; - return value; - })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, value, filters) { + value.filter = function(filter) { + var i = -1, n = filters.length; + while (++i < n) filters[i][3](filter); + value.exists(filter); + }; + return value; + })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_event_filter_expression() { - var cacheKey = 'event_filter_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_filter_operator(); - if (result3 !== null) { - var result4 = parse__(); - if (result4 !== null) { - if (input.substr(pos, 1) === "(") { - var result5 = "("; - pos += 1; + var result0, result1, result2, result3, result4, result5, result6, result7, result8, result9, result10; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse_filter_operator(); + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + if (input.charCodeAt(pos) === 40) { + result2 = "("; + pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"(\""); } } - if (result5 !== null) { - var result6 = parse__(); - if (result6 !== null) { - var result7 = parse_event_member_expression(); - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - if (input.substr(pos, 1) === ",") { - var result9 = ","; - pos += 1; + if (result2 !== null) { + result3 = parse__(); + if (result3 !== null) { + result4 = parse_event_member_expression(); + if (result4 !== null) { + result5 = parse__(); + if (result5 !== null) { + if (input.charCodeAt(pos) === 44) { + result6 = ","; + pos++; } else { - var result9 = null; - if (reportMatchFailures) { + result6 = null; + if (reportFailures === 0) { matchFailed("\",\""); } } - if (result9 !== null) { - var result10 = parse__(); - if (result10 !== null) { - var result11 = parse_literal(); - if (result11 !== null) { - var result12 = parse__(); - if (result12 !== null) { - if (input.substr(pos, 1) === ")") { - var result13 = ")"; - pos += 1; + if (result6 !== null) { + result7 = parse__(); + if (result7 !== null) { + result8 = parse_literal(); + if (result8 !== null) { + result9 = parse__(); + if (result9 !== null) { + if (input.charCodeAt(pos) === 41) { + result10 = ")"; + pos++; } else { - var result13 = null; - if (reportMatchFailures) { + result10 = null; + if (reportFailures === 0) { matchFailed("\")\""); } } - if (result13 !== null) { - var result1 = [result3, result4, result5, result6, result7, result8, result9, result10, result11, result12, result13]; + if (result10 !== null) { + result0 = [result0, result1, result2, result3, result4, result5, result6, result7, result8, result9, result10]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(op, member, value) { return function(o) { op(o, member.field, value); }; })(result1[0], result1[4], result1[8]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, op, member, value) { return function(o) { op(o, member.field, value); }; })(pos0, result0[0], result0[4], result0[8]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_event_value_expression() { - var cacheKey = 'event_value_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos1 = pos; - var savedPos2 = pos; - var result7 = parse_type(); - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - if (input.substr(pos, 1) === "(") { - var result9 = "("; - pos += 1; + var result0, result1, result2, result3, result4, result5, result6, result7, result8, result9; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + result0 = parse_type(); + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + if (input.charCodeAt(pos) === 40) { + result2 = "("; + pos++; } else { - var result9 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"(\""); } } - if (result9 !== null) { - var result10 = parse__(); - if (result10 !== null) { - var result11 = parse_event_member_expression(); - if (result11 !== null) { - var result12 = []; - var savedPos3 = pos; - var result16 = parse__(); - if (result16 !== null) { - if (input.substr(pos, 1) === ",") { - var result17 = ","; - pos += 1; + if (result2 !== null) { + result3 = parse__(); + if (result3 !== null) { + result4 = parse_event_member_expression(); + if (result4 !== null) { + result5 = []; + pos2 = pos; + result6 = parse__(); + if (result6 !== null) { + if (input.charCodeAt(pos) === 44) { + result7 = ","; + pos++; } else { - var result17 = null; - if (reportMatchFailures) { + result7 = null; + if (reportFailures === 0) { matchFailed("\",\""); } } - if (result17 !== null) { - var result18 = parse__(); - if (result18 !== null) { - var result19 = parse_event_member_expression(); - if (result19 !== null) { - var result15 = [result16, result17, result18, result19]; + if (result7 !== null) { + result8 = parse__(); + if (result8 !== null) { + result9 = parse_event_member_expression(); + if (result9 !== null) { + result6 = [result6, result7, result8, result9]; } else { - var result15 = null; - pos = savedPos3; + result6 = null; + pos = pos2; } } else { - var result15 = null; - pos = savedPos3; + result6 = null; + pos = pos2; } } else { - var result15 = null; - pos = savedPos3; + result6 = null; + pos = pos2; } } else { - var result15 = null; - pos = savedPos3; + result6 = null; + pos = pos2; } - while (result15 !== null) { - result12.push(result15); - var savedPos3 = pos; - var result16 = parse__(); - if (result16 !== null) { - if (input.substr(pos, 1) === ",") { - var result17 = ","; - pos += 1; + while (result6 !== null) { + result5.push(result6); + pos2 = pos; + result6 = parse__(); + if (result6 !== null) { + if (input.charCodeAt(pos) === 44) { + result7 = ","; + pos++; } else { - var result17 = null; - if (reportMatchFailures) { + result7 = null; + if (reportFailures === 0) { matchFailed("\",\""); } } - if (result17 !== null) { - var result18 = parse__(); - if (result18 !== null) { - var result19 = parse_event_member_expression(); - if (result19 !== null) { - var result15 = [result16, result17, result18, result19]; + if (result7 !== null) { + result8 = parse__(); + if (result8 !== null) { + result9 = parse_event_member_expression(); + if (result9 !== null) { + result6 = [result6, result7, result8, result9]; } else { - var result15 = null; - pos = savedPos3; + result6 = null; + pos = pos2; } } else { - var result15 = null; - pos = savedPos3; + result6 = null; + pos = pos2; } } else { - var result15 = null; - pos = savedPos3; + result6 = null; + pos = pos2; } } else { - var result15 = null; - pos = savedPos3; + result6 = null; + pos = pos2; } } - if (result12 !== null) { - var result13 = parse__(); - if (result13 !== null) { - if (input.substr(pos, 1) === ")") { - var result14 = ")"; - pos += 1; + if (result5 !== null) { + result6 = parse__(); + if (result6 !== null) { + if (input.charCodeAt(pos) === 41) { + result7 = ")"; + pos++; } else { - var result14 = null; - if (reportMatchFailures) { + result7 = null; + if (reportFailures === 0) { matchFailed("\")\""); } } - if (result14 !== null) { - var result5 = [result7, result8, result9, result10, result11, result12, result13, result14]; + if (result7 !== null) { + result0 = [result0, result1, result2, result3, result4, result5, result6, result7]; } else { - var result5 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } } else { - var result5 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } } else { - var result5 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } } else { - var result5 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } } else { - var result5 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } } else { - var result5 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } } else { - var result5 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } } else { - var result5 = null; - pos = savedPos2; - } - var result6 = result5 !== null - ? (function(type, head, tail) { return compoundFields(type, head, tail); })(result5[0], result5[4], result5[5]) - : null; - if (result6 !== null) { - var result4 = result6; - } else { - var result4 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - if (result4 !== null) { - var result0 = result4; - } else { - var savedPos0 = pos; - var result2 = parse_type(); - var result3 = result2 !== null - ? (function(type) { return {type: type, exists: noop, fields: noop}; })(result2) - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, type, head, tail) { return compoundFields(type, head, tail); })(pos0, result0[0], result0[4], result0[5]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + result0 = parse_type(); + if (result0 !== null) { + result0 = (function(offset, type) { return {type: type, exists: noop, fields: noop}; })(pos0, result0); + } + if (result0 === null) { + pos = pos0; } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_event_member_expression() { - var cacheKey = 'event_member_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_identifier(); - if (result3 !== null) { - var result4 = []; - var savedPos4 = pos; - var savedPos5 = pos; - var result16 = parse__(); - if (result16 !== null) { - if (input.substr(pos, 1) === "[") { - var result17 = "["; - pos += 1; + var result0, result1, result2, result3, result4, result5, result6, result7; + var pos0, pos1, pos2, pos3; + + pos0 = pos; + pos1 = pos; + result0 = parse_identifier(); + if (result0 !== null) { + result1 = []; + pos2 = pos; + pos3 = pos; + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 91) { + result3 = "["; + pos++; } else { - var result17 = null; - if (reportMatchFailures) { + result3 = null; + if (reportFailures === 0) { matchFailed("\"[\""); } } - if (result17 !== null) { - var result18 = parse__(); - if (result18 !== null) { - var result19 = parse_number(); - if (result19 !== null) { - var result20 = parse__(); - if (result20 !== null) { - if (input.substr(pos, 1) === "]") { - var result21 = "]"; - pos += 1; + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_number(); + if (result5 !== null) { + result6 = parse__(); + if (result6 !== null) { + if (input.charCodeAt(pos) === 93) { + result7 = "]"; + pos++; } else { - var result21 = null; - if (reportMatchFailures) { + result7 = null; + if (reportFailures === 0) { matchFailed("\"]\""); } } - if (result21 !== null) { - var result14 = [result16, result17, result18, result19, result20, result21]; + if (result7 !== null) { + result2 = [result2, result3, result4, result5, result6, result7]; } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; - } - var result15 = result14 !== null - ? (function(name) { return arrayAccessor(name); })(result14[3]) - : null; - if (result15 !== null) { - var result13 = result15; - } else { - var result13 = null; - pos = savedPos4; + result2 = null; + pos = pos3; } - if (result13 !== null) { - var result5 = result13; - } else { - var savedPos2 = pos; - var savedPos3 = pos; - var result9 = parse__(); - if (result9 !== null) { - if (input.substr(pos, 1) === ".") { - var result10 = "."; - pos += 1; + if (result2 !== null) { + result2 = (function(offset, name) { return arrayAccessor(name); })(pos2, result2[3]); + } + if (result2 === null) { + pos = pos2; + } + if (result2 === null) { + pos2 = pos; + pos3 = pos; + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 46) { + result3 = "."; + pos++; } else { - var result10 = null; - if (reportMatchFailures) { + result3 = null; + if (reportFailures === 0) { matchFailed("\".\""); } } - if (result10 !== null) { - var result11 = parse__(); - if (result11 !== null) { - var result12 = parse_identifier(); - if (result12 !== null) { - var result7 = [result9, result10, result11, result12]; + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_identifier(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } - var result8 = result7 !== null - ? (function(name) { return objectAccessor(name); })(result7[3]) - : null; - if (result8 !== null) { - var result6 = result8; - } else { - var result6 = null; - pos = savedPos2; + if (result2 !== null) { + result2 = (function(offset, name) { return objectAccessor(name); })(pos2, result2[3]); } - if (result6 !== null) { - var result5 = result6; - } else { - var result5 = null;; - }; - } - while (result5 !== null) { - result4.push(result5); - var savedPos4 = pos; - var savedPos5 = pos; - var result16 = parse__(); - if (result16 !== null) { - if (input.substr(pos, 1) === "[") { - var result17 = "["; - pos += 1; + if (result2 === null) { + pos = pos2; + } + } + while (result2 !== null) { + result1.push(result2); + pos2 = pos; + pos3 = pos; + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 91) { + result3 = "["; + pos++; } else { - var result17 = null; - if (reportMatchFailures) { + result3 = null; + if (reportFailures === 0) { matchFailed("\"[\""); } } - if (result17 !== null) { - var result18 = parse__(); - if (result18 !== null) { - var result19 = parse_number(); - if (result19 !== null) { - var result20 = parse__(); - if (result20 !== null) { - if (input.substr(pos, 1) === "]") { - var result21 = "]"; - pos += 1; + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_number(); + if (result5 !== null) { + result6 = parse__(); + if (result6 !== null) { + if (input.charCodeAt(pos) === 93) { + result7 = "]"; + pos++; } else { - var result21 = null; - if (reportMatchFailures) { + result7 = null; + if (reportFailures === 0) { matchFailed("\"]\""); } } - if (result21 !== null) { - var result14 = [result16, result17, result18, result19, result20, result21]; + if (result7 !== null) { + result2 = [result2, result3, result4, result5, result6, result7]; } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } - var result15 = result14 !== null - ? (function(name) { return arrayAccessor(name); })(result14[3]) - : null; - if (result15 !== null) { - var result13 = result15; - } else { - var result13 = null; - pos = savedPos4; + if (result2 !== null) { + result2 = (function(offset, name) { return arrayAccessor(name); })(pos2, result2[3]); } - if (result13 !== null) { - var result5 = result13; - } else { - var savedPos2 = pos; - var savedPos3 = pos; - var result9 = parse__(); - if (result9 !== null) { - if (input.substr(pos, 1) === ".") { - var result10 = "."; - pos += 1; + if (result2 === null) { + pos = pos2; + } + if (result2 === null) { + pos2 = pos; + pos3 = pos; + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 46) { + result3 = "."; + pos++; } else { - var result10 = null; - if (reportMatchFailures) { + result3 = null; + if (reportFailures === 0) { matchFailed("\".\""); } } - if (result10 !== null) { - var result11 = parse__(); - if (result11 !== null) { - var result12 = parse_identifier(); - if (result12 !== null) { - var result7 = [result9, result10, result11, result12]; + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_identifier(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } - var result8 = result7 !== null - ? (function(name) { return objectAccessor(name); })(result7[3]) - : null; - if (result8 !== null) { - var result6 = result8; - } else { - var result6 = null; - pos = savedPos2; + if (result2 !== null) { + result2 = (function(offset, name) { return objectAccessor(name); })(pos2, result2[3]); + } + if (result2 === null) { + pos = pos2; } - if (result6 !== null) { - var result5 = result6; - } else { - var result5 = null;; - }; } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(head, tail) { return member(head, tail); })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, head, tail) { return member(head, tail); })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_filter_operator() { - var cacheKey = 'filter_operator@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0; + var pos0; - var savedPos7 = pos; + pos0 = pos; if (input.substr(pos, 2) === "eq") { - var result23 = "eq"; + result0 = "eq"; pos += 2; } else { - var result23 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"eq\""); } } - var result24 = result23 !== null - ? (function() { return filterEqual; })() - : null; - if (result24 !== null) { - var result22 = result24; - } else { - var result22 = null; - pos = savedPos7; + if (result0 !== null) { + result0 = (function(offset) { return filterEqual; })(pos0); } - if (result22 !== null) { - var result0 = result22; - } else { - var savedPos6 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 2) === "gt") { - var result20 = "gt"; + result0 = "gt"; pos += 2; } else { - var result20 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"gt\""); } } - var result21 = result20 !== null - ? (function() { return filterGreater; })() - : null; - if (result21 !== null) { - var result19 = result21; - } else { - var result19 = null; - pos = savedPos6; + if (result0 !== null) { + result0 = (function(offset) { return filterGreater; })(pos0); } - if (result19 !== null) { - var result0 = result19; - } else { - var savedPos5 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 2) === "ge") { - var result17 = "ge"; + result0 = "ge"; pos += 2; } else { - var result17 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"ge\""); } } - var result18 = result17 !== null - ? (function() { return filterGreaterOrEqual; })() - : null; - if (result18 !== null) { - var result16 = result18; - } else { - var result16 = null; - pos = savedPos5; + if (result0 !== null) { + result0 = (function(offset) { return filterGreaterOrEqual; })(pos0); } - if (result16 !== null) { - var result0 = result16; - } else { - var savedPos4 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 2) === "lt") { - var result14 = "lt"; + result0 = "lt"; pos += 2; } else { - var result14 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"lt\""); } } - var result15 = result14 !== null - ? (function() { return filterLess; })() - : null; - if (result15 !== null) { - var result13 = result15; - } else { - var result13 = null; - pos = savedPos4; + if (result0 !== null) { + result0 = (function(offset) { return filterLess; })(pos0); } - if (result13 !== null) { - var result0 = result13; - } else { - var savedPos3 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 2) === "le") { - var result11 = "le"; + result0 = "le"; pos += 2; } else { - var result11 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"le\""); } } - var result12 = result11 !== null - ? (function() { return filterLessOrEqual; })() - : null; - if (result12 !== null) { - var result10 = result12; - } else { - var result10 = null; - pos = savedPos3; + if (result0 !== null) { + result0 = (function(offset) { return filterLessOrEqual; })(pos0); } - if (result10 !== null) { - var result0 = result10; - } else { - var savedPos2 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 2) === "ne") { - var result8 = "ne"; + result0 = "ne"; pos += 2; } else { - var result8 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"ne\""); } } - var result9 = result8 !== null - ? (function() { return filterNotEqual; })() - : null; - if (result9 !== null) { - var result7 = result9; - } else { - var result7 = null; - pos = savedPos2; + if (result0 !== null) { + result0 = (function(offset) { return filterNotEqual; })(pos0); } - if (result7 !== null) { - var result0 = result7; - } else { - var savedPos1 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 2) === "re") { - var result5 = "re"; + result0 = "re"; pos += 2; } else { - var result5 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"re\""); } } - var result6 = result5 !== null - ? (function() { return filterRegularExpression; })() - : null; - if (result6 !== null) { - var result4 = result6; - } else { - var result4 = null; - pos = savedPos1; + if (result0 !== null) { + result0 = (function(offset) { return filterRegularExpression; })(pos0); } - if (result4 !== null) { - var result0 = result4; - } else { - var savedPos0 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 2) === "in") { - var result2 = "in"; + result0 = "in"; pos += 2; } else { - var result2 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"in\""); } } - var result3 = result2 !== null - ? (function() { return filterIn; })() - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return filterIn; })(pos0); } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; - }; - }; - }; - }; + if (result0 === null) { + pos = pos0; + } + } + } + } + } + } + } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_type() { - var cacheKey = 'type@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos).match(/^[a-z]/) !== null) { - var result3 = input.charAt(pos); + pos0 = pos; + pos1 = pos; + if (/^[a-z]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[a-z]"); } } - if (result3 !== null) { - if (input.substr(pos).match(/^[a-zA-Z0-9_]/) !== null) { - var result5 = input.charAt(pos); + if (result0 !== null) { + if (/^[a-zA-Z0-9_]/.test(input.charAt(pos))) { + result2 = input.charAt(pos); pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("[a-zA-Z0-9_]"); } } - if (result5 !== null) { - var result4 = []; - while (result5 !== null) { - result4.push(result5); - if (input.substr(pos).match(/^[a-zA-Z0-9_]/) !== null) { - var result5 = input.charAt(pos); + if (result2 !== null) { + result1 = []; + while (result2 !== null) { + result1.push(result2); + if (/^[a-zA-Z0-9_]/.test(input.charAt(pos))) { + result2 = input.charAt(pos); pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("[a-zA-Z0-9_]"); } } } } else { - var result4 = null; + result1 = null; } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(first, rest) { return first + rest.join(""); })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, first, rest) { return first + rest.join(""); })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_identifier() { - var cacheKey = 'identifier@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0, result1, result2; + var pos0, pos1; - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos).match(/^[a-zA-Z_]/) !== null) { - var result3 = input.charAt(pos); + pos0 = pos; + pos1 = pos; + if (/^[a-zA-Z_]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[a-zA-Z_]"); } } - if (result3 !== null) { - var result4 = []; - if (input.substr(pos).match(/^[a-zA-Z0-9_$]/) !== null) { - var result5 = input.charAt(pos); + if (result0 !== null) { + result1 = []; + if (/^[a-zA-Z0-9_$]/.test(input.charAt(pos))) { + result2 = input.charAt(pos); pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("[a-zA-Z0-9_$]"); } } - while (result5 !== null) { - result4.push(result5); - if (input.substr(pos).match(/^[a-zA-Z0-9_$]/) !== null) { - var result5 = input.charAt(pos); + while (result2 !== null) { + result1.push(result2); + if (/^[a-zA-Z0-9_$]/.test(input.charAt(pos))) { + result2 = input.charAt(pos); pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("[a-zA-Z0-9_$]"); } } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(first, rest) { return first + rest.join(""); })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, first, rest) { return first + rest.join(""); })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_literal() { - var cacheKey = 'literal@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var result9 = parse_array_literal(); - if (result9 !== null) { - var result0 = result9; - } else { - var result8 = parse_string(); - if (result8 !== null) { - var result0 = result8; - } else { - var result7 = parse_number(); - if (result7 !== null) { - var result0 = result7; - } else { - var savedPos1 = pos; + var result0; + var pos0; + + result0 = parse_array_literal(); + if (result0 === null) { + result0 = parse_string(); + if (result0 === null) { + result0 = parse_number(); + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 4) === "true") { - var result5 = "true"; + result0 = "true"; pos += 4; } else { - var result5 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"true\""); } } - var result6 = result5 !== null - ? (function() { return true; })() - : null; - if (result6 !== null) { - var result4 = result6; - } else { - var result4 = null; - pos = savedPos1; + if (result0 !== null) { + result0 = (function(offset) { return true; })(pos0); } - if (result4 !== null) { - var result0 = result4; - } else { - var savedPos0 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 5) === "false") { - var result2 = "false"; + result0 = "false"; pos += 5; } else { - var result2 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"false\""); } } - var result3 = result2 !== null - ? (function() { return false; })() - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return false; })(pos0); } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; - }; + if (result0 === null) { + pos = pos0; + } + } + } + } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_array_literal() { - var cacheKey = 'array_literal@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2, result3, result4, result5, result6, result7; + var pos0, pos1, pos2; - - var savedPos2 = pos; - var savedPos3 = pos; - if (input.substr(pos, 1) === "[") { - var result10 = "["; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 91) { + result0 = "["; + pos++; } else { - var result10 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"[\""); } } - if (result10 !== null) { - var result11 = parse__(); - if (result11 !== null) { - var result12 = parse_literal(); - if (result12 !== null) { - var result13 = []; - var savedPos4 = pos; - var result17 = parse__(); - if (result17 !== null) { - if (input.substr(pos, 1) === ",") { - var result18 = ","; - pos += 1; + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + result2 = parse_literal(); + if (result2 !== null) { + result3 = []; + pos2 = pos; + result4 = parse__(); + if (result4 !== null) { + if (input.charCodeAt(pos) === 44) { + result5 = ","; + pos++; } else { - var result18 = null; - if (reportMatchFailures) { + result5 = null; + if (reportFailures === 0) { matchFailed("\",\""); } } - if (result18 !== null) { - var result19 = parse__(); - if (result19 !== null) { - var result20 = parse_literal(); - if (result20 !== null) { - var result16 = [result17, result18, result19, result20]; + if (result5 !== null) { + result6 = parse__(); + if (result6 !== null) { + result7 = parse_literal(); + if (result7 !== null) { + result4 = [result4, result5, result6, result7]; } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } - while (result16 !== null) { - result13.push(result16); - var savedPos4 = pos; - var result17 = parse__(); - if (result17 !== null) { - if (input.substr(pos, 1) === ",") { - var result18 = ","; - pos += 1; + while (result4 !== null) { + result3.push(result4); + pos2 = pos; + result4 = parse__(); + if (result4 !== null) { + if (input.charCodeAt(pos) === 44) { + result5 = ","; + pos++; } else { - var result18 = null; - if (reportMatchFailures) { + result5 = null; + if (reportFailures === 0) { matchFailed("\",\""); } } - if (result18 !== null) { - var result19 = parse__(); - if (result19 !== null) { - var result20 = parse_literal(); - if (result20 !== null) { - var result16 = [result17, result18, result19, result20]; + if (result5 !== null) { + result6 = parse__(); + if (result6 !== null) { + result7 = parse_literal(); + if (result7 !== null) { + result4 = [result4, result5, result6, result7]; } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } } - if (result13 !== null) { - var result14 = parse__(); - if (result14 !== null) { - if (input.substr(pos, 1) === "]") { - var result15 = "]"; - pos += 1; + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + if (input.charCodeAt(pos) === 93) { + result5 = "]"; + pos++; } else { - var result15 = null; - if (reportMatchFailures) { + result5 = null; + if (reportFailures === 0) { matchFailed("\"]\""); } } - if (result15 !== null) { - var result8 = [result10, result11, result12, result13, result14, result15]; + if (result5 !== null) { + result0 = [result0, result1, result2, result3, result4, result5]; } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result8 = null; - pos = savedPos3; - } - var result9 = result8 !== null - ? (function(first, rest) { return [first].concat(rest.map(function(d) { return d[3]; })); })(result8[2], result8[3]) - : null; - if (result9 !== null) { - var result7 = result9; - } else { - var result7 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } - if (result7 !== null) { - var result0 = result7; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "[") { - var result4 = "["; - pos += 1; + if (result0 !== null) { + result0 = (function(offset, first, rest) { return [first].concat(rest.map(function(d) { return d[3]; })); })(pos0, result0[2], result0[3]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 91) { + result0 = "["; + pos++; } else { - var result4 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"[\""); } } - if (result4 !== null) { - var result5 = parse__(); - if (result5 !== null) { - if (input.substr(pos, 1) === "]") { - var result6 = "]"; - pos += 1; + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + if (input.charCodeAt(pos) === 93) { + result2 = "]"; + pos++; } else { - var result6 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"]\""); } } - if (result6 !== null) { - var result2 = [result4, result5, result6]; + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; - } - var result3 = result2 !== null - ? (function() { return []; })() - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset) { return []; })(pos0); + } + if (result0 === null) { + pos = pos0; } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_string() { - var cacheKey = 'string@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - var savedReportMatchFailures = reportMatchFailures; - reportMatchFailures = false; - var savedPos2 = pos; - var savedPos3 = pos; - if (input.substr(pos, 1) === "\"") { - var result11 = "\""; - pos += 1; + var result0, result1, result2; + var pos0, pos1; + + reportFailures++; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 34) { + result0 = "\""; + pos++; } else { - var result11 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\"\""); } } - if (result11 !== null) { - var result12 = []; - var result14 = parse_double_string_char(); - while (result14 !== null) { - result12.push(result14); - var result14 = parse_double_string_char(); + if (result0 !== null) { + result1 = []; + result2 = parse_double_string_char(); + while (result2 !== null) { + result1.push(result2); + result2 = parse_double_string_char(); } - if (result12 !== null) { - if (input.substr(pos, 1) === "\"") { - var result13 = "\""; - pos += 1; + if (result1 !== null) { + if (input.charCodeAt(pos) === 34) { + result2 = "\""; + pos++; } else { - var result13 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"\\\"\""); } } - if (result13 !== null) { - var result9 = [result11, result12, result13]; + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result9 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result9 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result9 = null; - pos = savedPos3; - } - var result10 = result9 !== null - ? (function(chars) { return chars.join(""); })(result9[1]) - : null; - if (result10 !== null) { - var result8 = result10; - } else { - var result8 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } - if (result8 !== null) { - var result0 = result8; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "'") { - var result4 = "'"; - pos += 1; + if (result0 !== null) { + result0 = (function(offset, chars) { return chars.join(""); })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 39) { + result0 = "'"; + pos++; } else { - var result4 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"'\""); } } - if (result4 !== null) { - var result5 = []; - var result7 = parse_single_string_char(); - while (result7 !== null) { - result5.push(result7); - var result7 = parse_single_string_char(); + if (result0 !== null) { + result1 = []; + result2 = parse_single_string_char(); + while (result2 !== null) { + result1.push(result2); + result2 = parse_single_string_char(); } - if (result5 !== null) { - if (input.substr(pos, 1) === "'") { - var result6 = "'"; - pos += 1; + if (result1 !== null) { + if (input.charCodeAt(pos) === 39) { + result2 = "'"; + pos++; } else { - var result6 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"'\""); } } - if (result6 !== null) { - var result2 = [result4, result5, result6]; + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; - } - var result3 = result2 !== null - ? (function(chars) { return chars.join(""); })(result2[1]) - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, chars) { return chars.join(""); })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; } - reportMatchFailures = savedReportMatchFailures; - if (reportMatchFailures && result0 === null) { + reportFailures--; + if (reportFailures === 0 && result0 === null) { matchFailed("string"); } - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_double_string_char() { - var cacheKey = 'double_string_char@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos2 = pos; - var savedPos3 = pos; - var savedPos4 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - if (input.substr(pos, 1) === "\"") { - var result13 = "\""; - pos += 1; + var result0, result1; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + pos2 = pos; + reportFailures++; + if (input.charCodeAt(pos) === 34) { + result0 = "\""; + pos++; } else { - var result13 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\"\""); } } - if (result13 !== null) { - var result11 = result13; - } else { - if (input.substr(pos, 1) === "\\") { - var result12 = "\\"; - pos += 1; + if (result0 === null) { + if (input.charCodeAt(pos) === 92) { + result0 = "\\"; + pos++; } else { - var result12 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\\""); } } - if (result12 !== null) { - var result11 = result12; - } else { - var result11 = null;; - }; } - reportMatchFailures = savedReportMatchFailuresVar0; - if (result11 === null) { - var result9 = ''; + reportFailures--; + if (result0 === null) { + result0 = ""; } else { - var result9 = null; - pos = savedPos4; + result0 = null; + pos = pos2; } - if (result9 !== null) { + if (result0 !== null) { if (input.length > pos) { - var result10 = input.charAt(pos); + result1 = input.charAt(pos); pos++; } else { - var result10 = null; - if (reportMatchFailures) { - matchFailed('any character'); + result1 = null; + if (reportFailures === 0) { + matchFailed("any character"); } } - if (result10 !== null) { - var result7 = [result9, result10]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result7 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result7 = null; - pos = savedPos3; - } - var result8 = result7 !== null - ? (function(char_) { return char_; })(result7[1]) - : null; - if (result8 !== null) { - var result6 = result8; - } else { - var result6 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } - if (result6 !== null) { - var result0 = result6; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "\\") { - var result4 = "\\"; - pos += 1; + if (result0 !== null) { + result0 = (function(offset, char_) { return char_; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 92) { + result0 = "\\"; + pos++; } else { - var result4 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\\""); } } - if (result4 !== null) { - var result5 = parse_escape_sequence(); - if (result5 !== null) { - var result2 = [result4, result5]; + if (result0 !== null) { + result1 = parse_escape_sequence(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; - } - var result3 = result2 !== null - ? (function(sequence) { return sequence; })(result2[1]) - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, sequence) { return sequence; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_single_string_char() { - var cacheKey = 'single_string_char@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos2 = pos; - var savedPos3 = pos; - var savedPos4 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - if (input.substr(pos, 1) === "'") { - var result13 = "'"; - pos += 1; + var result0, result1; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + pos2 = pos; + reportFailures++; + if (input.charCodeAt(pos) === 39) { + result0 = "'"; + pos++; } else { - var result13 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"'\""); } } - if (result13 !== null) { - var result11 = result13; - } else { - if (input.substr(pos, 1) === "\\") { - var result12 = "\\"; - pos += 1; + if (result0 === null) { + if (input.charCodeAt(pos) === 92) { + result0 = "\\"; + pos++; } else { - var result12 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\\""); } } - if (result12 !== null) { - var result11 = result12; - } else { - var result11 = null;; - }; } - reportMatchFailures = savedReportMatchFailuresVar0; - if (result11 === null) { - var result9 = ''; + reportFailures--; + if (result0 === null) { + result0 = ""; } else { - var result9 = null; - pos = savedPos4; + result0 = null; + pos = pos2; } - if (result9 !== null) { + if (result0 !== null) { if (input.length > pos) { - var result10 = input.charAt(pos); + result1 = input.charAt(pos); pos++; } else { - var result10 = null; - if (reportMatchFailures) { - matchFailed('any character'); + result1 = null; + if (reportFailures === 0) { + matchFailed("any character"); } } - if (result10 !== null) { - var result7 = [result9, result10]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result7 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result7 = null; - pos = savedPos3; - } - var result8 = result7 !== null - ? (function(char_) { return char_; })(result7[1]) - : null; - if (result8 !== null) { - var result6 = result8; - } else { - var result6 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } - if (result6 !== null) { - var result0 = result6; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "\\") { - var result4 = "\\"; - pos += 1; + if (result0 !== null) { + result0 = (function(offset, char_) { return char_; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 92) { + result0 = "\\"; + pos++; } else { - var result4 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\\""); } } - if (result4 !== null) { - var result5 = parse_escape_sequence(); - if (result5 !== null) { - var result2 = [result4, result5]; + if (result0 !== null) { + result1 = parse_escape_sequence(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; - } - var result3 = result2 !== null - ? (function(sequence) { return sequence; })(result2[1]) - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, sequence) { return sequence; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_escape_sequence() { - var cacheKey = 'escape_sequence@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var result9 = parse_character_escape_sequence(); - if (result9 !== null) { - var result0 = result9; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "0") { - var result6 = "0"; - pos += 1; + var result0, result1; + var pos0, pos1, pos2; + + result0 = parse_character_escape_sequence(); + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 48) { + result0 = "0"; + pos++; } else { - var result6 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"0\""); } } - if (result6 !== null) { - var savedPos2 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - var result8 = parse_digit(); - reportMatchFailures = savedReportMatchFailuresVar0; - if (result8 === null) { - var result7 = ''; + if (result0 !== null) { + pos2 = pos; + reportFailures++; + result1 = parse_digit(); + reportFailures--; + if (result1 === null) { + result1 = ""; } else { - var result7 = null; - pos = savedPos2; + result1 = null; + pos = pos2; } - if (result7 !== null) { - var result4 = [result6, result7]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result4 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result4 = null; - pos = savedPos1; - } - var result5 = result4 !== null - ? (function() { return "\0"; })() - : null; - if (result5 !== null) { - var result3 = result5; - } else { - var result3 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset) { return "\0"; })(pos0); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + result0 = parse_hex_escape_sequence(); + if (result0 === null) { + result0 = parse_unicode_escape_sequence(); + } } - if (result3 !== null) { - var result0 = result3; - } else { - var result2 = parse_hex_escape_sequence(); - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_unicode_escape_sequence(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_character_escape_sequence() { - var cacheKey = 'character_escape_sequence@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0; - var result2 = parse_single_escape_character(); - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_non_escape_character(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; + result0 = parse_single_escape_character(); + if (result0 === null) { + result0 = parse_non_escape_character(); } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_single_escape_character() { - var cacheKey = 'single_escape_character@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; + var pos0; - - var savedPos0 = pos; - if (input.substr(pos).match(/^['"\\bfnrtv]/) !== null) { - var result1 = input.charAt(pos); + pos0 = pos; + if (/^['"\\bfnrtv]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result1 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("['\"\\\\bfnrtv]"); } } - var result2 = result1 !== null - ? (function(char_) { return char_.replace("b", "\b").replace("f", "\f").replace("n", "\n").replace("r", "\r").replace("t", "\t").replace("v", "\x0B"); })(result1) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, char_) { return char_.replace("b", "\b").replace("f", "\f").replace("n", "\n").replace("r", "\r").replace("t", "\t").replace("v", "\x0B"); })(pos0, result0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_non_escape_character() { - var cacheKey = 'non_escape_character@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var savedPos2 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - var result5 = parse_escape_character(); - reportMatchFailures = savedReportMatchFailuresVar0; - if (result5 === null) { - var result3 = ''; - } else { - var result3 = null; - pos = savedPos2; - } - if (result3 !== null) { + var result0, result1; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + pos2 = pos; + reportFailures++; + result0 = parse_escape_character(); + reportFailures--; + if (result0 === null) { + result0 = ""; + } else { + result0 = null; + pos = pos2; + } + if (result0 !== null) { if (input.length > pos) { - var result4 = input.charAt(pos); + result1 = input.charAt(pos); pos++; } else { - var result4 = null; - if (reportMatchFailures) { - matchFailed('any character'); + result1 = null; + if (reportFailures === 0) { + matchFailed("any character"); } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(char_) { return char_; })(result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, char_) { return char_; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_escape_character() { - var cacheKey = 'escape_character@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var result4 = parse_single_escape_character(); - if (result4 !== null) { - var result0 = result4; - } else { - var result3 = parse_digit(); - if (result3 !== null) { - var result0 = result3; - } else { - if (input.substr(pos, 1) === "x") { - var result2 = "x"; - pos += 1; + var result0; + + result0 = parse_single_escape_character(); + if (result0 === null) { + result0 = parse_digit(); + if (result0 === null) { + if (input.charCodeAt(pos) === 120) { + result0 = "x"; + pos++; } else { - var result2 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"x\""); } } - if (result2 !== null) { - var result0 = result2; - } else { - if (input.substr(pos, 1) === "u") { - var result1 = "u"; - pos += 1; + if (result0 === null) { + if (input.charCodeAt(pos) === 117) { + result0 = "u"; + pos++; } else { - var result1 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"u\""); } } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; + } + } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_hex_escape_sequence() { - var cacheKey = 'hex_escape_sequence@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0, result1, result2; + var pos0, pos1; - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "x") { - var result3 = "x"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 120) { + result0 = "x"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"x\""); } } - if (result3 !== null) { - var result4 = parse_hex_digit(); - if (result4 !== null) { - var result5 = parse_hex_digit(); - if (result5 !== null) { - var result1 = [result3, result4, result5]; + if (result0 !== null) { + result1 = parse_hex_digit(); + if (result1 !== null) { + result2 = parse_hex_digit(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(h1, h2) { return String.fromCharCode(+("0x" + h1 + h2)); })(result1[1], result1[2]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, h1, h2) { return String.fromCharCode(+("0x" + h1 + h2)); })(pos0, result0[1], result0[2]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_unicode_escape_sequence() { - var cacheKey = 'unicode_escape_sequence@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0, result1, result2, result3, result4; + var pos0, pos1; - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "u") { - var result3 = "u"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 117) { + result0 = "u"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"u\""); } } - if (result3 !== null) { - var result4 = parse_hex_digit(); - if (result4 !== null) { - var result5 = parse_hex_digit(); - if (result5 !== null) { - var result6 = parse_hex_digit(); - if (result6 !== null) { - var result7 = parse_hex_digit(); - if (result7 !== null) { - var result1 = [result3, result4, result5, result6, result7]; + if (result0 !== null) { + result1 = parse_hex_digit(); + if (result1 !== null) { + result2 = parse_hex_digit(); + if (result2 !== null) { + result3 = parse_hex_digit(); + if (result3 !== null) { + result4 = parse_hex_digit(); + if (result4 !== null) { + result0 = [result0, result1, result2, result3, result4]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(h1, h2, h3, h4) { return String.fromCharCode(+("0x" + h1 + h2 + h3 + h4)); })(result1[1], result1[2], result1[3], result1[4]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, h1, h2, h3, h4) { return String.fromCharCode(+("0x" + h1 + h2 + h3 + h4)); })(pos0, result0[1], result0[2], result0[3], result0[4]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_number() { - var cacheKey = 'number@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - var savedReportMatchFailures = reportMatchFailures; - reportMatchFailures = false; - var savedPos8 = pos; - var savedPos9 = pos; - if (input.substr(pos, 1) === "-") { - var result26 = "-"; - pos += 1; + var result0, result1, result2; + var pos0, pos1; + + reportFailures++; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 45) { + result0 = "-"; + pos++; } else { - var result26 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"-\""); } } - if (result26 !== null) { - var result27 = parse__(); - if (result27 !== null) { - var result28 = parse_number(); - if (result28 !== null) { - var result24 = [result26, result27, result28]; + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + result2 = parse_number(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result24 = null; - pos = savedPos9; + result0 = null; + pos = pos1; } } else { - var result24 = null; - pos = savedPos9; + result0 = null; + pos = pos1; } } else { - var result24 = null; - pos = savedPos9; - } - var result25 = result24 !== null - ? (function(number) { return -number; })(result24[2]) - : null; - if (result25 !== null) { - var result23 = result25; - } else { - var result23 = null; - pos = savedPos8; + result0 = null; + pos = pos1; } - if (result23 !== null) { - var result0 = result23; - } else { - var savedPos6 = pos; - var savedPos7 = pos; - var result20 = parse_int(); - if (result20 !== null) { - var result21 = parse_frac(); - if (result21 !== null) { - var result22 = parse_exp(); - if (result22 !== null) { - var result18 = [result20, result21, result22]; + if (result0 !== null) { + result0 = (function(offset, number) { return -number; })(pos0, result0[2]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + result0 = parse_int(); + if (result0 !== null) { + result1 = parse_frac(); + if (result1 !== null) { + result2 = parse_exp(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result18 = null; - pos = savedPos7; + result0 = null; + pos = pos1; } } else { - var result18 = null; - pos = savedPos7; + result0 = null; + pos = pos1; } } else { - var result18 = null; - pos = savedPos7; - } - var result19 = result18 !== null - ? (function(int_, frac, exp) { return +(int_ + frac + exp); })(result18[0], result18[1], result18[2]) - : null; - if (result19 !== null) { - var result17 = result19; - } else { - var result17 = null; - pos = savedPos6; + result0 = null; + pos = pos1; } - if (result17 !== null) { - var result0 = result17; - } else { - var savedPos4 = pos; - var savedPos5 = pos; - var result15 = parse_int(); - if (result15 !== null) { - var result16 = parse_frac(); - if (result16 !== null) { - var result13 = [result15, result16]; + if (result0 !== null) { + result0 = (function(offset, int_, frac, exp) { return +(int_ + frac + exp); })(pos0, result0[0], result0[1], result0[2]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + result0 = parse_int(); + if (result0 !== null) { + result1 = parse_frac(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result13 = null; - pos = savedPos5; + result0 = null; + pos = pos1; } } else { - var result13 = null; - pos = savedPos5; + result0 = null; + pos = pos1; } - var result14 = result13 !== null - ? (function(int_, frac) { return +(int_ + frac); })(result13[0], result13[1]) - : null; - if (result14 !== null) { - var result12 = result14; - } else { - var result12 = null; - pos = savedPos4; + if (result0 !== null) { + result0 = (function(offset, int_, frac) { return +(int_ + frac); })(pos0, result0[0], result0[1]); } - if (result12 !== null) { - var result0 = result12; - } else { - var savedPos2 = pos; - var savedPos3 = pos; - var result10 = parse_int(); - if (result10 !== null) { - var result11 = parse_exp(); - if (result11 !== null) { - var result8 = [result10, result11]; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + result0 = parse_int(); + if (result0 !== null) { + result1 = parse_exp(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } - var result9 = result8 !== null - ? (function(int_, exp) { return +(int_ + exp); })(result8[0], result8[1]) - : null; - if (result9 !== null) { - var result7 = result9; - } else { - var result7 = null; - pos = savedPos2; + if (result0 !== null) { + result0 = (function(offset, int_, exp) { return +(int_ + exp); })(pos0, result0[0], result0[1]); } - if (result7 !== null) { - var result0 = result7; - } else { - var savedPos1 = pos; - var result5 = parse_frac(); - var result6 = result5 !== null - ? (function(frac) { return +frac; })(result5) - : null; - if (result6 !== null) { - var result4 = result6; - } else { - var result4 = null; - pos = savedPos1; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + result0 = parse_frac(); + if (result0 !== null) { + result0 = (function(offset, frac) { return +frac; })(pos0, result0); } - if (result4 !== null) { - var result0 = result4; - } else { - var savedPos0 = pos; - var result2 = parse_int(); - var result3 = result2 !== null - ? (function(int_) { return +int_; })(result2) - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + result0 = parse_int(); + if (result0 !== null) { + result0 = (function(offset, int_) { return +int_; })(pos0, result0); } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; - }; - }; + if (result0 === null) { + pos = pos0; + } + } + } + } + } } - reportMatchFailures = savedReportMatchFailures; - if (reportMatchFailures && result0 === null) { + reportFailures--; + if (reportFailures === 0 && result0 === null) { matchFailed("number"); } - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_int() { - var cacheKey = 'int@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result5 = parse_digit19(); - if (result5 !== null) { - var result6 = parse_digits(); - if (result6 !== null) { - var result3 = [result5, result6]; + var result0, result1; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse_digit19(); + if (result0 !== null) { + result1 = parse_digits(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result3 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result3 = null; - pos = savedPos1; - } - var result4 = result3 !== null - ? (function(digit19, digits) { return digit19 + digits; })(result3[0], result3[1]) - : null; - if (result4 !== null) { - var result2 = result4; - } else { - var result2 = null; - pos = savedPos0; + result0 = null; + pos = pos1; } - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_digit(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; + if (result0 !== null) { + result0 = (function(offset, digit19, digits) { return digit19 + digits; })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + result0 = parse_digit(); } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_frac() { - var cacheKey = 'frac@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === ".") { - var result3 = "."; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 46) { + result0 = "."; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\".\""); } } - if (result3 !== null) { - var result4 = parse_digits(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse_digits(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(digits) { return "." + digits; })(result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, digits) { return "." + digits; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_exp() { - var cacheKey = 'exp@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_e(); - if (result3 !== null) { - var result4 = parse_digits(); - if (result4 !== null) { - var result1 = [result3, result4]; + var result0, result1; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse_e(); + if (result0 !== null) { + result1 = parse_digits(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(e, digits) { return e + digits; })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, e, digits) { return e + digits; })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_digits() { - var cacheKey = 'digits@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0, result1; + var pos0; - var savedPos0 = pos; - var result3 = parse_digit(); - if (result3 !== null) { - var result1 = []; - while (result3 !== null) { - result1.push(result3); - var result3 = parse_digit(); + pos0 = pos; + result1 = parse_digit(); + if (result1 !== null) { + result0 = []; + while (result1 !== null) { + result0.push(result1); + result1 = parse_digit(); } } else { - var result1 = null; + result0 = null; } - var result2 = result1 !== null - ? (function(digits) { return digits.join(""); })(result1) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, digits) { return digits.join(""); })(pos0, result0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_e() { - var cacheKey = 'e@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos).match(/^[eE]/) !== null) { - var result3 = input.charAt(pos); + pos0 = pos; + pos1 = pos; + if (/^[eE]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[eE]"); } } - if (result3 !== null) { - if (input.substr(pos).match(/^[+\-]/) !== null) { - var result5 = input.charAt(pos); + if (result0 !== null) { + if (/^[+\-]/.test(input.charAt(pos))) { + result1 = input.charAt(pos); pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result1 = null; + if (reportFailures === 0) { matchFailed("[+\\-]"); } } - var result4 = result5 !== null ? result5 : ''; - if (result4 !== null) { - var result1 = [result3, result4]; + result1 = result1 !== null ? result1 : ""; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(e, sign) { return e + sign; })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, e, sign) { return e + sign; })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_digit() { - var cacheKey = 'digit@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; - - if (input.substr(pos).match(/^[0-9]/) !== null) { - var result0 = input.charAt(pos); + if (/^[0-9]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[0-9]"); } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_digit19() { - var cacheKey = 'digit19@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; - - if (input.substr(pos).match(/^[1-9]/) !== null) { - var result0 = input.charAt(pos); + if (/^[1-9]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[1-9]"); } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_hex_digit() { - var cacheKey = 'hex_digit@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0; - if (input.substr(pos).match(/^[0-9a-fA-F]/) !== null) { - var result0 = input.charAt(pos); + if (/^[0-9a-fA-F]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[0-9a-fA-F]"); } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse__() { - var cacheKey = '_@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; - var savedReportMatchFailures = reportMatchFailures; - reportMatchFailures = false; - var result0 = []; - var result1 = parse_whitespace(); + reportFailures++; + result0 = []; + result1 = parse_whitespace(); while (result1 !== null) { result0.push(result1); - var result1 = parse_whitespace(); + result1 = parse_whitespace(); } - reportMatchFailures = savedReportMatchFailures; - if (reportMatchFailures && result0 === null) { + reportFailures--; + if (reportFailures === 0 && result0 === null) { matchFailed("whitespace"); } - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_whitespace() { - var cacheKey = 'whitespace@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0; - if (input.substr(pos).match(/^[ \n\r]/) !== null) { - var result0 = input.charAt(pos); + if (/^[ \t\n\r]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { - matchFailed("[ \\n\\r]"); + result0 = null; + if (reportFailures === 0) { + matchFailed("[ \\t\\n\\r]"); } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } - function buildErrorMessage() { - function buildExpected(failuresExpected) { - failuresExpected.sort(); - - var lastFailure = null; - var failuresExpectedUnique = []; - for (var i = 0; i < failuresExpected.length; i++) { - if (failuresExpected[i] !== lastFailure) { - failuresExpectedUnique.push(failuresExpected[i]); - lastFailure = failuresExpected[i]; - } - } - - switch (failuresExpectedUnique.length) { - case 0: - return 'end of input'; - case 1: - return failuresExpectedUnique[0]; - default: - return failuresExpectedUnique.slice(0, failuresExpectedUnique.length - 1).join(', ') - + ' or ' - + failuresExpectedUnique[failuresExpectedUnique.length - 1]; + + function cleanupExpected(expected) { + expected.sort(); + + var lastExpected = null; + var cleanExpected = []; + for (var i = 0; i < expected.length; i++) { + if (expected[i] !== lastExpected) { + cleanExpected.push(expected[i]); + lastExpected = expected[i]; } } - - var expected = buildExpected(rightmostMatchFailuresExpected); - var actualPos = Math.max(pos, rightmostMatchFailuresPos); - var actual = actualPos < input.length - ? quote(input.charAt(actualPos)) - : 'end of input'; - - return 'Expected ' + expected + ' but ' + actual + ' found.'; + return cleanExpected; } function computeErrorPosition() { @@ -2908,13 +2240,13 @@ module.exports = (function(){ var column = 1; var seenCR = false; - for (var i = 0; i < rightmostMatchFailuresPos; i++) { + for (var i = 0; i < Math.max(pos, rightmostFailuresPos); i++) { var ch = input.charAt(i); - if (ch === '\n') { + if (ch === "\n") { if (!seenCR) { line++; } column = 1; seenCR = false; - } else if (ch === '\r' | ch === '\u2028' || ch === '\u2029') { + } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") { line++; column = 1; seenCR = true; @@ -2928,146 +2260,76 @@ module.exports = (function(){ } - - var filterEqual = function(o, k, v) { o[k] = v; }, - - filterGreater = filter("$gt"), - - filterGreaterOrEqual = filter("$gte"), - - filterLess = filter("$lt"), - - filterLessOrEqual = filter("$lte"), - - filterNotEqual = filter("$ne"), - - filterRegularExpression = filter("$regex"), - - filterIn = filter("$in"), - - exists = {$exists: true}; - - - - function noop() {} - - - - function filter(op) { - - return function(o, k, v) { - - var f = o[k]; - - switch (typeof f) { - - case "undefined": o[k] = f = {}; // continue - - case "object": f[op] = v; break; - - // otherwise, observe the existing equals (literal) filter - + var filterEqual = function(o, k, v) { o[k] = v; }, + filterGreater = filter("$gt"), + filterGreaterOrEqual = filter("$gte"), + filterLess = filter("$lt"), + filterLessOrEqual = filter("$lte"), + filterNotEqual = filter("$ne"), + filterRegularExpression = filter("$regex"), + filterIn = filter("$in"), + exists = {$exists: true}; + + function noop() {} + + function filter(op) { + return function(o, k, v) { + var f = o[k]; + switch (typeof f) { + case "undefined": o[k] = f = {}; // continue + case "object": f[op] = v; break; + // otherwise, observe the existing equals (literal) filter + } + }; } - }; - - } - - - - function arrayAccessor(name) { - - name = new String(name); - - name.array = true; - - return name; - - } - - - - function objectAccessor(name) { - - return name; - - } - - - - function compoundFields(type, head, tail) { - - var n = tail.length; - - return { - - type: type, - - exists: function(o) { - - var i = -1; - - head.exists(o); - - while (++i < n) tail[i][3].exists(o); - - }, - - fields: function(o) { - - var i = -1; - - head.fields(o); - - while (++i < n) tail[i][3].fields(o); - + function arrayAccessor(name) { + name = new String(name); + name.array = true; + return name; } - }; - - } - - - - function member(head, tail) { - - var fields = ["d", head].concat(tail), - - shortName = fields.filter(function(d) { return !d.array; }).join("."), - - longName = fields.join("."), - - i = -1, - - n = fields.length; - - return { - - field: longName, - - exists: function(o) { - - if (!(shortName in o)) { - - o[shortName] = exists; - - } - - }, - - fields: function(o) { - - o[shortName] = 1; - + function objectAccessor(name) { + return name; } - }; + function compoundFields(type, head, tail) { + var n = tail.length; + return { + type: type, + exists: function(o) { + var i = -1; + head.exists(o); + while (++i < n) tail[i][3].exists(o); + }, + fields: function(o) { + var i = -1; + head.fields(o); + while (++i < n) tail[i][3].fields(o); + } + }; + } - } + function member(head, tail) { + var fields = ["d", head].concat(tail), + shortName = fields.filter(function(d) { return !d.array; }).join("."), + longName = fields.join("."), + i = -1, + n = fields.length; + return { + field: longName, + exists: function(o) { + if (!(shortName in o)) { + o[shortName] = exists; + } + }, + fields: function(o) { + o[shortName] = 1; + } + }; + } - - var result = parseFunctions[startRule](); @@ -3078,27 +2340,32 @@ module.exports = (function(){ * * - |result !== null| * - |pos === input.length| - * - |rightmostMatchFailuresExpected| may or may not contain something + * - |rightmostFailuresExpected| may or may not contain something * * 2. The parser successfully parsed only a part of the input. * * - |result !== null| * - |pos < input.length| - * - |rightmostMatchFailuresExpected| may or may not contain something + * - |rightmostFailuresExpected| may or may not contain something * * 3. The parser did not successfully parse any part of the input. * * - |result === null| * - |pos === 0| - * - |rightmostMatchFailuresExpected| contains at least one failure + * - |rightmostFailuresExpected| contains at least one failure * * All code following this comment (including called functions) must * handle these states. */ if (result === null || pos !== input.length) { + var offset = Math.max(pos, rightmostFailuresPos); + var found = offset < input.length ? input.charAt(offset) : null; var errorPosition = computeErrorPosition(); + throw new this.SyntaxError( - buildErrorMessage(), + cleanupExpected(rightmostFailuresExpected), + found, + offset, errorPosition.line, errorPosition.column ); @@ -3113,9 +2380,33 @@ module.exports = (function(){ /* Thrown when a parser encounters a syntax error. */ - result.SyntaxError = function(message, line, column) { - this.name = 'SyntaxError'; - this.message = message; + result.SyntaxError = function(expected, found, offset, line, column) { + function buildMessage(expected, found) { + var expectedHumanized, foundHumanized; + + switch (expected.length) { + case 0: + expectedHumanized = "end of input"; + break; + case 1: + expectedHumanized = expected[0]; + break; + default: + expectedHumanized = expected.slice(0, expected.length - 1).join(", ") + + " or " + + expected[expected.length - 1]; + } + + foundHumanized = found ? quote(found) : "end of input"; + + return "Expected " + expectedHumanized + " but " + foundHumanized + " found."; + } + + this.name = "SyntaxError"; + this.expected = expected; + this.found = found; + this.message = buildMessage(expected, found); + this.offset = offset; this.line = line; this.column = column; }; diff --git a/lib/cube/event.js b/lib/cube/event.js index a8d4c42a..cc90cbd0 100644 --- a/lib/cube/event.js +++ b/lib/cube/event.js @@ -1,148 +1,107 @@ +'use strict'; + // TODO include the event._id (and define a JSON encoding for ObjectId?) // TODO allow the event time to change when updating (fix invalidation) -var mongodb = require("mongodb"), - parser = require("./event-expression"), - tiers = require("./tiers"), - types = require("./types"), - bisect = require("./bisect"), - ObjectID = mongodb.ObjectID; - -var type_re = /^[a-z][a-zA-Z0-9_]+$/, - invalidate = {$set: {i: true}}, - multi = {multi: true}, - metric_options = {capped: true, size: 1e7, autoIndexId: true}; +var _ = require("underscore"), + mongodb = require("mongodb"), + ObjectID = mongodb.ObjectID, + util = require("util"), + Event = require("./models/event"), + Metric = require("./models/metric"), + Invalidator = Metric = require("./models/invalidator"), + parser = require("./event-expression"), + bisect = require("./bisect"), + metalog = require("./metalog"), + config = require("./config"); // When streaming events, we should allow a delay for events to arrive, or else // we risk skipping events that arrive after their event.time. This delay can be // customized by specifying a `delay` property as part of the request. var streamDelayDefault = 5000, - streamInterval = 1000; + streamInterval = 1000; + +// serial id so we can track flushers +var putter_id = 0; -// How frequently to invalidate metrics after receiving events. -var invalidateInterval = 5000; +// event.putter -- save the event, invalidate any cached metrics impacted by it. +// +// @param request -- +// - id, a unique ID (optional). If included, it will be used as the Mongo record's primary key -- if the collector receives that event multiple times, it will only be stored once. If omitted, Mongo will generate a unique ID for you. +// - time, timestamp for the event (a date-formatted string) +// - type, namespace for the events. A corresponding `foo_events` collection must exist in the DB -- /schema/schema-*.js illustrate how to set up a new event type. +// - data, the event's payload +// -exports.putter = function(db) { - var collection = types(db), - knownByType = {}, - eventsToSaveByType = {}, - timesToInvalidateByTierByType = {}; +exports.putter = function(db){ + var horizons = config.horizons; + var invalidator = new Invalidator(); - function putter(request, callback) { + function putter(request, callback){ var time = "time" in request ? new Date(request.time) : new Date(), type = request.type; + callback = callback || function(){}; - // Validate the date and type. - if (!type_re.test(type)) return callback({error: "invalid type"}), -1; - if (isNaN(time)) return callback({error: "invalid time"}), -1; - - // If an id is specified, promote it to Mongo's primary key. - var event = {t: time, d: request.data}; - if ("id" in request) event._id = request.id; - - // If this is a known event type, save immediately. - if (type in knownByType) return save(type, event); - - // If someone is already creating the event collection for this new type, - // then append this event to the queue for later save. - if (type in eventsToSaveByType) return eventsToSaveByType[type].push(event); - - // Otherwise, it's up to us to see if the collection exists, verify the - // associated indexes, create the corresponding metrics collection, and save - // any events that have queued up in the interim! - - // First add the new event to the queue. - eventsToSaveByType[type] = [event]; - - // If the events collection exists, then we assume the metrics & indexes do - // too. Otherwise, we must create the required collections and indexes. Note - // that if you want to customize the size of the capped metrics collection, - // or add custom indexes, you can still do all that by hand. - db.collectionNames(type + "_events", function(error, names) { - var events = collection(type).events; - if (names.length) return saveEvents(); - - // Events are indexed by time. - events.ensureIndex({"t": 1}, handle); - - // Create a capped collection for metrics. Three indexes are required: one - // for finding metrics, one (_id) for updating, and one for invalidation. - db.createCollection(type + "_metrics", metric_options, function(error, metrics) { - handle(error); - metrics.ensureIndex({"i": 1, "_id.e": 1, "_id.l": 1, "_id.t": 1}, handle); - metrics.ensureIndex({"i": 1, "_id.l": 1, "_id.t": 1}, handle); - saveEvents(); - }); + // // Drop events from before invalidation horizon + if ((! request.force) && horizons && (time < new Date(new Date() - horizons.invalidation))) { + metalog.info('cube_compute', {error: "event before invalidation horizon"}); + return callback({error: "event before invalidation horizon"}), -1; + } - // Save any pending events to the new collection. - function saveEvents() { - knownByType[type] = true; - eventsToSaveByType[type].forEach(function(event) { save(type, event); }); - delete eventsToSaveByType[type]; - } + var event = new Event(type, time, request.data, request.id); + + // Save the event, then queue invalidation of its associated cached metrics. + // + // We don't invalidate the events immediately. This would cause redundant + // updates when many events are received simultaneously. Also, having a + // short delay between saving the event and invalidating the metrics reduces + // the likelihood of a race condition between when the events are read by + // the evaluator and when the newly-computed metrics are saved. + event.save(db, function after_save(error, event){ + if (error) return callback({error: error}); + if (event) invalidator.add(event.type, event); + callback(event); }); } - // Save the event of the specified type, and queue invalidation of any cached - // metrics associated with this event type and time. - // - // We don't invalidate the events immediately. This would cause many redundant - // updates when many events are received simultaneously. Also, having a short - // delay between saving the event and invalidating the metrics reduces the - // likelihood of a race condition between when the events are read by the - // evaluator and when the newly-computed metrics are saved. - function save(type, event) { - collection(type).events.save(event, handle); - queueInvalidation(type, event); - } - - // Schedule deferred invalidation of metrics for this type. - // For each type and tier, track the metric times to invalidate. - // The times are kept in sorted order for bisection. - function queueInvalidation(type, event) { - var timesToInvalidateByTier = timesToInvalidateByTierByType[type], - time = event.t; - if (timesToInvalidateByTier) { - for (var tier in tiers) { - var tierTimes = timesToInvalidateByTier[tier], - tierTime = tiers[tier].floor(time), - i = bisect(tierTimes, tierTime); - if (i >= tierTimes.length) tierTimes.push(tierTime); - else if (tierTimes[i] > tierTime) tierTimes.splice(i, 0, tierTime); - } - } else { - timesToInvalidateByTier = timesToInvalidateByTierByType[type] = {}; - for (var tier in tiers) { - timesToInvalidateByTier[tier] = [tiers[tier].floor(time)]; - } - } - } + putter.id = ++putter_id; // Process any deferred metric invalidations, flushing the queues. Note that // the queue (timesToInvalidateByTierByType) is copied-on-write, so while the // previous batch of events are being invalidated, new events can arrive. - setInterval(function() { - for (var type in timesToInvalidateByTierByType) { - var metrics = collection(type).metrics, - timesToInvalidateByTier = timesToInvalidateByTierByType[type]; - for (var tier in tiers) { - metrics.update({ - i: false, - "_id.l": +tier, - "_id.t": {$in: timesToInvalidateByTier[tier]} - }, invalidate, multi, handle); - } - flushed = true; - } - timesToInvalidateByTierByType = {}; // copy-on-write - }, invalidateInterval); + Invalidator.start_flusher(putter.id, function(){ + if (db.isHalted) return putter.stop(); + invalidator.flush(db, handle); + invalidator = new Invalidator(); // copy-on-write + }); + + putter.invalidator = function(){ return invalidator; }; + putter.stop = function(on_stop){ + metalog.info('putter_stopping', {id: putter.id}); + Invalidator.stop_flusher(putter.id, on_stop); + invalidator = null + }; + metalog.info('putter_start', {id: putter.id, inv: invalidator}); return putter; }; +// -------------------------------------------------------------------------- + +// +// event.getter - subscribe to event type +// +// if `stop` is not given, does a streaming response, polling for results every +// `streamDelay` (5 seconds). +// +// if `stop` is given, return events from the given interval +// +// * convert the request expression and filters into a MongoDB-ready query +// * Issue the query; +// * if streaming, register the query to be run at a regular interval +// exports.getter = function(db) { - var collection = types(db), - streamsBySource = {}; + var streamsBySource = {}; function getter(request, callback) { var stream = !("stop" in request), @@ -152,44 +111,46 @@ exports.getter = function(db) { // Validate the dates. if (isNaN(start)) return callback({error: "invalid start"}), -1; - if (isNaN(stop)) return callback({error: "invalid stop"}), -1; + if (isNaN(stop)) return callback({error: "invalid stop"}), -1; // Parse the expression. var expression; try { expression = parser.parse(request.expression); } catch (error) { - return callback({error: "invalid expression"}), -1; + var resp = { error: "invalid expression", expression: request.expression, message: error }; + metalog.info('event_getter', resp); + return callback(resp), -1; } // Set an optional limit on the number of events to return. var options = {sort: {t: -1}, batchSize: 1000}; if ("limit" in request) options.limit = +request.limit; - // Copy any expression filters into the query object. var filter = {t: {$gte: start, $lt: stop}}; expression.filter(filter); - // Request any needed fields. var fields = {t: 1}; expression.fields(fields); // Query for the desired events. function query(callback) { - collection(expression.type).events.find(filter, fields, options, function(error, cursor) { + db.events(expression.type, function(error, collection){ handle(error); - cursor.each(function(error, event) { - - // If the callback is closed (i.e., if the WebSocket connection was - // closed), then abort the query. Note that closing the cursor mid- - // loop causes an error, which we subsequently ignore! - if (callback.closed) return cursor.close(); - + collection.find(filter, fields, options, function(error, cursor) { handle(error); + cursor.each(function(error, event) { + // If the callback is closed (i.e., if the WebSocket connection was + // closed), then abort the query. Note that closing the cursor mid- + // loop causes an error, which we subsequently ignore! + if (callback.closed) return cursor.close(); + + handle(error); - // A null event indicates that there are no more results. - if (event) callback({id: event._id instanceof ObjectID ? undefined : event._id, time: event.t, data: event.d}); - else callback(null); + // A null event indicates that there are no more results. + if (event) callback({id: event._id instanceof ObjectID ? undefined : event._id, time: event.t, data: event.d}); + else callback(null); + }); }); }); } @@ -253,16 +214,20 @@ exports.getter = function(db) { } getter.close = function(callback) { + // as results or periodic calls trigger in the future, ensure that they quit + // listening and drop further results on the floor. callback.closed = true; }; return getter; }; -function handle(error) { - if (error) throw error; -} - function open(callback) { return !callback.closed; } + +function handle(error) { + if (!error) return; + metalog.error('event', error); + throw error; +} diff --git a/lib/cube/index.js b/lib/cube/index.js index af67319e..ef9c31b8 100644 --- a/lib/cube/index.js +++ b/lib/cube/index.js @@ -1,5 +1,12 @@ -exports.emitter = require("./emitter"); -exports.server = require("./server"); -exports.collector = require("./collector"); -exports.evaluator = require("./evaluator"); -exports.endpoint = require("./endpoint"); +'use strict'; +process.env.TZ = 'UTC'; + +exports.authentication = require("./authentication"); +exports.metalog = require("./metalog"); +exports.emitter = require("./emitter"); +exports.server = require("./server"); +exports.collector = require("./collector"); +exports.evaluator = require("./evaluator"); +exports.visualizer = require("./visualizer"); +exports.endpoint = require("./endpoint"); +exports.warmer = require("./warmer"); diff --git a/lib/cube/metalog.js b/lib/cube/metalog.js new file mode 100644 index 00000000..7f5fef24 --- /dev/null +++ b/lib/cube/metalog.js @@ -0,0 +1,165 @@ +'use strict'; + +// metalog -- log, trace and cubify internal processing stages +// +// * log progress to disk +// * trace a request through the process stack +// * + +var util = require("util"), + _ = require("underscore"); + +// 2012-08-10 04:25:55.736 +function timestamp() { + function pad(n){ return n < 10 ? '0' + n.toString(10) : n.toString(10); } + var d = new Date(); + var time = [pad(d.getUTCHours()), pad(d.getUTCMinutes()), pad(d.getUTCSeconds())].join(':'); + var date = [d.getUTCFullYear(), pad(d.getUTCMonth()+1), pad(d.getUTCDate()) ].join('-'); + return [date, ' ', time, '.', d.getUTCMilliseconds()].join(''); +} + +var metalog = { + putter: null, + log: function(msg){ process.stderr.write(timestamp() + ' - ' + msg.toString() + "\n"); }, + silent: function(){ } +}; + + +var tracing = false; // false to mute tracing + +// adjust verboseness by reassigning `metalog.loggers.{level}` +// @example: quiet all logs +// metalog.loggers.info = metalog.silent(); +metalog.loggers = { + info: metalog.log, + minor: metalog.silent +}; + +// if true, cubify `metalog.event`s +metalog.send_events = true; + +// -------------------------------------------------------------------------- + +// Cubify an event and (optionally) log it. The last parameter specifies the +// logger -- 'info' (the default), 'minor' or 'silent'. +metalog.event = function(label, hsh, logger){ + metalog[logger||"info"](label, hsh); + metalog.cubify(label, hsh); +}; + +metalog.cubify = function(label, hsh){ + if ((! metalog.send_events) || (! metalog.putter)) return; + hsh.at = label; + metalog.putter({ type: 'cube', time: Date.now(), data: hsh }); +} + +metalog.logify = function logify(label, hsh, logger){ + hsh = hsh || {}; + try{ + logger(label + "\t" + JSON.stringify(hsh)); + } catch(error) { + logger(label + "\t" + util.inspect(hsh)); + } +} + +// -------------------------------------------------------------------------- + +// Always goes thru to metalog.log +metalog.warn = function(label, hsh){ metalog.logify(label, hsh, metalog.log); }; + +// Events important enough for the production log file. Does not cubify. +metalog.info = function(label, hsh){ metalog.logify(label, hsh, metalog.loggers.info); }; + +// Debug-level statements; loggers.minor is typically mapped to 'silent'. +metalog.minor = function(label, hsh){ metalog.logify(label, hsh, metalog.loggers.minor); }; + +// log an error; always goes thru to metalog.log +metalog.error = function(at, error, info){ + info = _.extend((info||{}), { at: at, error: error.message, stack: error.stack, code: error.status }); + metalog.logify('error', info, metalog.log); +}; + +// -------------------------------------------------------------------------- + +var trace_id = 1, boot = +(new Date()); + +var dump_keys = {tid: 4, beg: 15, boot: 4, + tier: 8, start: 9, stop: 9, bin: 9, + mget: 4, find: 4, mf0: 4, mf1: 4, msb: 4, + mflt: 4, mfl0: 4, mfly: 4, msav: 4, mres: 4, resp: 4, + expr: 12}; + +function dump(hsh){ + var value = hsh.val; + var fields = _.map(dump_keys, function(len, key){ return ((hsh[key] === undefined ? '' : hsh[key])+' ').slice(0,len); }); + var extras = {}; for (var key in hsh){ if (!(key in dump_keys)) extras[key] = hsh[key]; } + if (_.isNumber(value)) fields.push((value*100)/100); + // metalog.log(fields.join('|') + "\t" + (JSON.stringify(extras).slice(0,150))); +} + +function dump_header(){ + // metalog.log(_.map(dump_keys, function(len, key){ return (key+' ').slice(0,len); }).join('|')); +} + +if (tracing){ + + metalog.trace = function(label, item, hsh){ + + // try{ throw new Error("Trace"); } catch(e){ console.log(e.stack); } + + item = item || {}; hsh = hsh || {}; + var using = (hsh.using||{}); delete hsh.using; + if (! item._trace) item._trace = { beg: +(new Date()), boot: ((new Date()) - boot) }; + // + item._trace = _.extend(item._trace, (using._trace||{}), hsh); + // + item._trace[label] = ((new Date()) - item._trace['beg']); + if (! item._trace.tid) item._trace.tid = trace_id++; + + return item; + }; + + metalog.dump_trace = function(label, item, hsh){ + var item = metalog.trace(label, item, hsh); + var tr = item._trace; + function p(v,l){ var str = (v ? v.toString() : ''); return (v+'................').slice(0,l); } + try{ + item._trace['end'] = +(new Date()); + if (Math.random() < 0.3) dump_header(); + dump(item._trace); + } catch(err){ metalog.log(err); metalog.log(err.stack); } + return item; + }; + +} else { + metalog.trace = function(){}; + metalog.dump_trace = function(){}; +} + + +// Dump the 'util.inspect' view of each argument to the console. +metalog.inspectify = function inspectify(args){ + for (var idx in arguments) { + process.stderr.write(idx + ": "); + var val = arguments[idx]; + if (_.isFunction(val)) val = val.toString().slice(0, 80); + process.stderr.write(util.inspect(val, false, 2, true)+"\n"); // , null, true + } + process.stderr.write('----\n'); +}; + +// wraps a callback, inspectifies its contents as it goes by +// @example +// metalog.spy(callback, 'unary', this); +// @example you don't have to supply anything but the callback +// metalog.spy(function(err, val){ ... }) +metalog.spy = function spy(callback, label, ctxt){ + if (! label) label = 'spyable'; + if (! ctxt) ctxt = null; + return function(){ + process.stderr.write(label+': ', callback, ' called with ', util.inspect(arguments), '\n'); + return callback.apply(ctxt, arguments); + }; +}; + +module.exports = metalog; diff --git a/lib/cube/metric-expression.js b/lib/cube/metric-expression.js index c0e15b8d..ebd8a1c5 100644 --- a/lib/cube/metric-expression.js +++ b/lib/cube/metric-expression.js @@ -1,5 +1,32 @@ module.exports = (function(){ - /* Generated by PEG.js 0.6.2 (http://pegjs.majda.cz/). */ + /* + * Generated by PEG.js 0.7.0. + * + * http://pegjs.majda.cz/ + */ + + function quote(s) { + /* + * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a + * string literal except for the closing quote character, backslash, + * carriage return, line separator, paragraph separator, and line feed. + * Any character may appear in the form of an escape sequence. + * + * For portability, we also escape escape all control and non-ASCII + * characters. Note that "\0" and "\v" escape sequences are not used + * because JSHint does not like the first and IE the second. + */ + return '"' + s + .replace(/\\/g, '\\\\') // backslash + .replace(/"/g, '\\"') // closing quote character + .replace(/\x08/g, '\\b') // backspace + .replace(/\t/g, '\\t') // horizontal tab + .replace(/\n/g, '\\n') // line feed + .replace(/\f/g, '\\f') // form feed + .replace(/\r/g, '\\r') // carriage return + .replace(/[\x00-\x07\x0B\x0E-\x1F\x80-\uFFFF]/g, escape) + + '"'; + } var result = { /* @@ -10,47 +37,48 @@ module.exports = (function(){ */ parse: function(input, startRule) { var parseFunctions = { - "_": parse__, - "additive_operator": parse_additive_operator, - "array_literal": parse_array_literal, - "character_escape_sequence": parse_character_escape_sequence, - "digit": parse_digit, - "digit19": parse_digit19, - "digits": parse_digits, - "double_string_char": parse_double_string_char, - "e": parse_e, - "escape_character": parse_escape_character, - "escape_sequence": parse_escape_sequence, - "event_additive_expression": parse_event_additive_expression, + "start": parse_start, + "metric_additive_expression": parse_metric_additive_expression, + "metric_multiplicative_expression": parse_metric_multiplicative_expression, + "metric_unary_expression": parse_metric_unary_expression, + "metric_primary_expression": parse_metric_primary_expression, + "metric_group_expression": parse_metric_group_expression, "event_expression": parse_event_expression, "event_filter_expression": parse_event_filter_expression, - "event_member_expression": parse_event_member_expression, + "event_value_expression": parse_event_value_expression, + "event_additive_expression": parse_event_additive_expression, "event_multiplicative_expression": parse_event_multiplicative_expression, - "event_primary_expression": parse_event_primary_expression, "event_unary_expression": parse_event_unary_expression, - "event_value_expression": parse_event_value_expression, - "exp": parse_exp, + "event_primary_expression": parse_event_primary_expression, + "event_member_expression": parse_event_member_expression, + "additive_operator": parse_additive_operator, + "multiplicative_operator": parse_multiplicative_operator, "filter_operator": parse_filter_operator, - "frac": parse_frac, - "hex_digit": parse_hex_digit, - "hex_escape_sequence": parse_hex_escape_sequence, + "reduce": parse_reduce, + "type": parse_type, "identifier": parse_identifier, - "int": parse_int, "literal": parse_literal, - "metric_additive_expression": parse_metric_additive_expression, - "metric_multiplicative_expression": parse_metric_multiplicative_expression, - "metric_primary_expression": parse_metric_primary_expression, - "metric_unary_expression": parse_metric_unary_expression, - "multiplicative_operator": parse_multiplicative_operator, - "non_escape_character": parse_non_escape_character, - "number": parse_number, - "reduce": parse_reduce, - "single_escape_character": parse_single_escape_character, - "single_string_char": parse_single_string_char, - "start": parse_start, + "array_literal": parse_array_literal, "string": parse_string, - "type": parse_type, + "double_string_char": parse_double_string_char, + "single_string_char": parse_single_string_char, + "escape_sequence": parse_escape_sequence, + "character_escape_sequence": parse_character_escape_sequence, + "single_escape_character": parse_single_escape_character, + "non_escape_character": parse_non_escape_character, + "escape_character": parse_escape_character, + "hex_escape_sequence": parse_hex_escape_sequence, "unicode_escape_sequence": parse_unicode_escape_sequence, + "number": parse_number, + "int": parse_int, + "frac": parse_frac, + "exp": parse_exp, + "digits": parse_digits, + "e": parse_e, + "digit": parse_digit, + "digit19": parse_digit19, + "hex_digit": parse_hex_digit, + "_": parse__, "whitespace": parse_whitespace }; @@ -63,10 +91,9 @@ module.exports = (function(){ } var pos = 0; - var reportMatchFailures = true; - var rightmostMatchFailuresPos = 0; - var rightmostMatchFailuresExpected = []; - var cache = {}; + var reportFailures = 0; + var rightmostFailuresPos = 0; + var rightmostFailuresExpected = []; function padLeft(input, padding, length) { var result = input; @@ -81,3769 +108,2960 @@ module.exports = (function(){ function escape(ch) { var charCode = ch.charCodeAt(0); + var escapeChar; + var length; if (charCode <= 0xFF) { - var escapeChar = 'x'; - var length = 2; + escapeChar = 'x'; + length = 2; } else { - var escapeChar = 'u'; - var length = 4; + escapeChar = 'u'; + length = 4; } return '\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), '0', length); } - function quote(s) { - /* - * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a - * string literal except for the closing quote character, backslash, - * carriage return, line separator, paragraph separator, and line feed. - * Any character may appear in the form of an escape sequence. - */ - return '"' + s - .replace(/\\/g, '\\\\') // backslash - .replace(/"/g, '\\"') // closing quote character - .replace(/\r/g, '\\r') // carriage return - .replace(/\n/g, '\\n') // line feed - .replace(/[\x80-\uFFFF]/g, escape) // non-ASCII characters - + '"'; - } - function matchFailed(failure) { - if (pos < rightmostMatchFailuresPos) { + if (pos < rightmostFailuresPos) { return; } - if (pos > rightmostMatchFailuresPos) { - rightmostMatchFailuresPos = pos; - rightmostMatchFailuresExpected = []; + if (pos > rightmostFailuresPos) { + rightmostFailuresPos = pos; + rightmostFailuresExpected = []; } - rightmostMatchFailuresExpected.push(failure); + rightmostFailuresExpected.push(failure); } function parse_start() { - var cacheKey = 'start@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse__(); - if (result3 !== null) { - var result4 = parse_metric_additive_expression(); - if (result4 !== null) { - var result5 = parse__(); - if (result5 !== null) { - var result1 = [result3, result4, result5]; + var result0, result1, result2; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse__(); + if (result0 !== null) { + result1 = parse_metric_additive_expression(); + if (result1 !== null) { + result2 = parse__(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(expression) { return expression; })(result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, expression) { return expression; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_metric_additive_expression() { - var cacheKey = 'metric_additive_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_metric_multiplicative_expression(); - if (result3 !== null) { - var result4 = []; - var savedPos2 = pos; - var result6 = parse__(); - if (result6 !== null) { - var result7 = parse_additive_operator(); - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - var result9 = parse_metric_additive_expression(); - if (result9 !== null) { - var result5 = [result6, result7, result8, result9]; + var result0, result1, result2, result3, result4, result5; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + result0 = parse_metric_multiplicative_expression(); + if (result0 !== null) { + result1 = []; + pos2 = pos; + result2 = parse__(); + if (result2 !== null) { + result3 = parse_additive_operator(); + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_metric_additive_expression(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; - } - while (result5 !== null) { - result4.push(result5); - var savedPos2 = pos; - var result6 = parse__(); - if (result6 !== null) { - var result7 = parse_additive_operator(); - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - var result9 = parse_metric_additive_expression(); - if (result9 !== null) { - var result5 = [result6, result7, result8, result9]; + result2 = null; + pos = pos2; + } + while (result2 !== null) { + result1.push(result2); + pos2 = pos; + result2 = parse__(); + if (result2 !== null) { + result3 = parse_additive_operator(); + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_metric_additive_expression(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(head, tail) { return compoundMetric(head, tail); })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, head, tail) { return compoundMetric(head, tail); })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_metric_multiplicative_expression() { - var cacheKey = 'metric_multiplicative_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_metric_unary_expression(); - if (result3 !== null) { - var result4 = []; - var savedPos2 = pos; - var result6 = parse__(); - if (result6 !== null) { - var result7 = parse_multiplicative_operator(); - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - var result9 = parse_metric_multiplicative_expression(); - if (result9 !== null) { - var result5 = [result6, result7, result8, result9]; + var result0, result1, result2, result3, result4, result5; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + result0 = parse_metric_unary_expression(); + if (result0 !== null) { + result1 = []; + pos2 = pos; + result2 = parse__(); + if (result2 !== null) { + result3 = parse_multiplicative_operator(); + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_metric_multiplicative_expression(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; - } - while (result5 !== null) { - result4.push(result5); - var savedPos2 = pos; - var result6 = parse__(); - if (result6 !== null) { - var result7 = parse_multiplicative_operator(); - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - var result9 = parse_metric_multiplicative_expression(); - if (result9 !== null) { - var result5 = [result6, result7, result8, result9]; + result2 = null; + pos = pos2; + } + while (result2 !== null) { + result1.push(result2); + pos2 = pos; + result2 = parse__(); + if (result2 !== null) { + result3 = parse_multiplicative_operator(); + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_metric_multiplicative_expression(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(head, tail) { return compoundMetric(head, tail); })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, head, tail) { return compoundMetric(head, tail); })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_metric_unary_expression() { - var cacheKey = 'metric_unary_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0, result1, result2; + var pos0, pos1; - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "-") { - var result5 = "-"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 45) { + result0 = "-"; + pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"-\""); } } - if (result5 !== null) { - var result6 = parse__(); - if (result6 !== null) { - var result7 = parse_metric_unary_expression(); - if (result7 !== null) { - var result3 = [result5, result6, result7]; + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + result2 = parse_metric_unary_expression(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result3 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result3 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result3 = null; - pos = savedPos1; - } - var result4 = result3 !== null - ? (function(expression) { var value = expression.value; expression.value = function(o) { return -value(o); }; if (expression.source) expression.source = "-" + expression.source; return expression; })(result3[2]) - : null; - if (result4 !== null) { - var result2 = result4; - } else { - var result2 = null; - pos = savedPos0; + result0 = null; + pos = pos1; } - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_metric_primary_expression(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; + if (result0 !== null) { + result0 = (function(offset, expression) { var value = expression.value; expression.value = function(o) { return -value(o); }; if (expression.source) expression.source = "-" + expression.source; return expression; })(pos0, result0[2]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + result0 = parse_metric_primary_expression(); } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_metric_primary_expression() { - var cacheKey = 'metric_primary_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos3 = pos; - var savedPos4 = pos; - var result15 = parse_reduce(); - if (result15 !== null) { - var result16 = parse__(); - if (result16 !== null) { - if (input.substr(pos, 1) === "(") { - var result17 = "("; - pos += 1; + var result0, result1, result2, result3, result4, result5, result6, result7, result8, result9, result10; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + result0 = parse_reduce(); + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + if (input.charCodeAt(pos) === 40) { + result2 = "("; + pos++; } else { - var result17 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"(\""); } } - if (result17 !== null) { - var result18 = parse__(); - if (result18 !== null) { - var result19 = parse_event_expression(); - if (result19 !== null) { - var result20 = parse__(); - if (result20 !== null) { - if (input.substr(pos, 1) === ")") { - var result21 = ")"; - pos += 1; + if (result2 !== null) { + result3 = parse__(); + if (result3 !== null) { + result4 = parse_event_expression(); + if (result4 !== null) { + result5 = parse__(); + if (result5 !== null) { + if (input.charCodeAt(pos) === 41) { + result6 = ")"; + pos++; } else { - var result21 = null; - if (reportMatchFailures) { + result6 = null; + if (reportFailures === 0) { matchFailed("\")\""); } } - if (result21 !== null) { - var result13 = [result15, result16, result17, result18, result19, result20, result21]; + if (result6 !== null) { + pos2 = pos; + result7 = parse__(); + if (result7 !== null) { + if (input.charCodeAt(pos) === 46) { + result8 = "."; + pos++; + } else { + result8 = null; + if (reportFailures === 0) { + matchFailed("\".\""); + } + } + if (result8 !== null) { + result9 = parse__(); + if (result9 !== null) { + result10 = parse_metric_group_expression(); + if (result10 !== null) { + result7 = [result7, result8, result9, result10]; + } else { + result7 = null; + pos = pos2; + } + } else { + result7 = null; + pos = pos2; + } + } else { + result7 = null; + pos = pos2; + } + } else { + result7 = null; + pos = pos2; + } + result7 = result7 !== null ? result7 : ""; + if (result7 !== null) { + result0 = [result0, result1, result2, result3, result4, result5, result6, result7]; + } else { + result0 = null; + pos = pos1; + } } else { - var result13 = null; - pos = savedPos4; + result0 = null; + pos = pos1; } } else { - var result13 = null; - pos = savedPos4; + result0 = null; + pos = pos1; } } else { - var result13 = null; - pos = savedPos4; + result0 = null; + pos = pos1; } } else { - var result13 = null; - pos = savedPos4; + result0 = null; + pos = pos1; } } else { - var result13 = null; - pos = savedPos4; + result0 = null; + pos = pos1; } } else { - var result13 = null; - pos = savedPos4; + result0 = null; + pos = pos1; } } else { - var result13 = null; - pos = savedPos4; - } - var result14 = result13 !== null - ? (function(reduce, event) { event.reduce = reduce; event.source = input.substring(savedPos3, pos); return event; })(result13[0], result13[4]) - : null; - if (result14 !== null) { - var result12 = result14; - } else { - var result12 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } - if (result12 !== null) { - var result0 = result12; - } else { - var savedPos2 = pos; - var result10 = parse_number(); - var result11 = result10 !== null - ? (function(value) { return {value: function() { return value; }}; })(result10) - : null; - if (result11 !== null) { - var result9 = result11; - } else { - var result9 = null; - pos = savedPos2; + if (result0 !== null) { + result0 = (function(offset, reduce, event, group) { event.reduce = reduce; event.source = input.substring(pos0, pos); if(group) event.group = group[3]; return event; })(pos0, result0[0], result0[4], result0[7]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + result0 = parse_number(); + if (result0 !== null) { + result0 = (function(offset, value) { return {value: function() { return value; }}; })(pos0, result0); } - if (result9 !== null) { - var result0 = result9; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "(") { - var result4 = "("; - pos += 1; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 40) { + result0 = "("; + pos++; } else { - var result4 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"(\""); } } - if (result4 !== null) { - var result5 = parse__(); - if (result5 !== null) { - var result6 = parse_metric_additive_expression(); - if (result6 !== null) { - var result7 = parse__(); - if (result7 !== null) { - if (input.substr(pos, 1) === ")") { - var result8 = ")"; - pos += 1; + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + result2 = parse_metric_additive_expression(); + if (result2 !== null) { + result3 = parse__(); + if (result3 !== null) { + if (input.charCodeAt(pos) === 41) { + result4 = ")"; + pos++; } else { - var result8 = null; - if (reportMatchFailures) { + result4 = null; + if (reportFailures === 0) { matchFailed("\")\""); } } - if (result8 !== null) { - var result2 = [result4, result5, result6, result7, result8]; + if (result4 !== null) { + result0 = [result0, result1, result2, result3, result4]; } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result3 = result2 !== null - ? (function(expression) { return expression; })(result2[2]) - : null; - if (result3 !== null) { - var result1 = result3; + if (result0 !== null) { + result0 = (function(offset, expression) { return expression; })(pos0, result0[2]); + } + if (result0 === null) { + pos = pos0; + } + } + } + return result0; + } + + function parse_metric_group_expression() { + var result0, result1, result2, result3, result4, result5, result6; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + if (input.substr(pos, 5) === "group") { + result0 = "group"; + pos += 5; + } else { + result0 = null; + if (reportFailures === 0) { + matchFailed("\"group\""); + } + } + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + if (input.charCodeAt(pos) === 40) { + result2 = "("; + pos++; } else { - var result1 = null; - pos = savedPos0; + result2 = null; + if (reportFailures === 0) { + matchFailed("\"(\""); + } } - if (result1 !== null) { - var result0 = result1; + if (result2 !== null) { + result3 = parse__(); + if (result3 !== null) { + result4 = parse_event_member_expression(); + if (result4 !== null) { + result5 = parse__(); + if (result5 !== null) { + if (input.charCodeAt(pos) === 41) { + result6 = ")"; + pos++; + } else { + result6 = null; + if (reportFailures === 0) { + matchFailed("\")\""); + } + } + if (result6 !== null) { + result0 = [result0, result1, result2, result3, result4, result5, result6]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } } else { - var result0 = null;; - }; - }; + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, member) { return member; })(pos0, result0[4]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_event_expression() { - var cacheKey = 'event_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_event_value_expression(); - if (result3 !== null) { - var result4 = []; - var savedPos2 = pos; - var result6 = parse__(); - if (result6 !== null) { - if (input.substr(pos, 1) === ".") { - var result7 = "."; - pos += 1; + var result0, result1, result2, result3, result4, result5; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + result0 = parse_event_value_expression(); + if (result0 !== null) { + result1 = []; + pos2 = pos; + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 46) { + result3 = "."; + pos++; } else { - var result7 = null; - if (reportMatchFailures) { + result3 = null; + if (reportFailures === 0) { matchFailed("\".\""); } } - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - var result9 = parse_event_filter_expression(); - if (result9 !== null) { - var result5 = [result6, result7, result8, result9]; + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_event_filter_expression(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; - } - while (result5 !== null) { - result4.push(result5); - var savedPos2 = pos; - var result6 = parse__(); - if (result6 !== null) { - if (input.substr(pos, 1) === ".") { - var result7 = "."; - pos += 1; + result2 = null; + pos = pos2; + } + while (result2 !== null) { + result1.push(result2); + pos2 = pos; + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 46) { + result3 = "."; + pos++; } else { - var result7 = null; - if (reportMatchFailures) { + result3 = null; + if (reportFailures === 0) { matchFailed("\".\""); } } - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - var result9 = parse_event_filter_expression(); - if (result9 !== null) { - var result5 = [result6, result7, result8, result9]; + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_event_filter_expression(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(value, filters) { - value.filter = function(filter) { - var i = -1, n = filters.length; - while (++i < n) filters[i][3](filter); - value.exists(filter); - }; - return value; - })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, value, filters) { + value.filter = function(filter) { + var i = -1, n = filters.length; + while (++i < n) filters[i][3](filter); + value.exists(filter); + }; + return value; + })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_event_filter_expression() { - var cacheKey = 'event_filter_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_filter_operator(); - if (result3 !== null) { - var result4 = parse__(); - if (result4 !== null) { - if (input.substr(pos, 1) === "(") { - var result5 = "("; - pos += 1; + var result0, result1, result2, result3, result4, result5, result6, result7, result8, result9, result10; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse_filter_operator(); + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + if (input.charCodeAt(pos) === 40) { + result2 = "("; + pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"(\""); } } - if (result5 !== null) { - var result6 = parse__(); - if (result6 !== null) { - var result7 = parse_event_member_expression(); - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - if (input.substr(pos, 1) === ",") { - var result9 = ","; - pos += 1; + if (result2 !== null) { + result3 = parse__(); + if (result3 !== null) { + result4 = parse_event_member_expression(); + if (result4 !== null) { + result5 = parse__(); + if (result5 !== null) { + if (input.charCodeAt(pos) === 44) { + result6 = ","; + pos++; } else { - var result9 = null; - if (reportMatchFailures) { + result6 = null; + if (reportFailures === 0) { matchFailed("\",\""); } } - if (result9 !== null) { - var result10 = parse__(); - if (result10 !== null) { - var result11 = parse_literal(); - if (result11 !== null) { - var result12 = parse__(); - if (result12 !== null) { - if (input.substr(pos, 1) === ")") { - var result13 = ")"; - pos += 1; + if (result6 !== null) { + result7 = parse__(); + if (result7 !== null) { + result8 = parse_literal(); + if (result8 !== null) { + result9 = parse__(); + if (result9 !== null) { + if (input.charCodeAt(pos) === 41) { + result10 = ")"; + pos++; } else { - var result13 = null; - if (reportMatchFailures) { + result10 = null; + if (reportFailures === 0) { matchFailed("\")\""); } } - if (result13 !== null) { - var result1 = [result3, result4, result5, result6, result7, result8, result9, result10, result11, result12, result13]; + if (result10 !== null) { + result0 = [result0, result1, result2, result3, result4, result5, result6, result7, result8, result9, result10]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(op, member, value) { return function(o) { op(o, member.field, value); }; })(result1[0], result1[4], result1[8]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, op, member, value) { return function(o) { op(o, member.field, value); }; })(pos0, result0[0], result0[4], result0[8]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_event_value_expression() { - var cacheKey = 'event_value_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos1 = pos; - var savedPos2 = pos; - var result7 = parse_type(); - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - if (input.substr(pos, 1) === "(") { - var result9 = "("; - pos += 1; + var result0, result1, result2, result3, result4, result5, result6; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse_type(); + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + if (input.charCodeAt(pos) === 40) { + result2 = "("; + pos++; } else { - var result9 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"(\""); } } - if (result9 !== null) { - var result10 = parse__(); - if (result10 !== null) { - var result11 = parse_event_additive_expression(); - if (result11 !== null) { - var result12 = parse__(); - if (result12 !== null) { - if (input.substr(pos, 1) === ")") { - var result13 = ")"; - pos += 1; + if (result2 !== null) { + result3 = parse__(); + if (result3 !== null) { + result4 = parse_event_additive_expression(); + if (result4 !== null) { + result5 = parse__(); + if (result5 !== null) { + if (input.charCodeAt(pos) === 41) { + result6 = ")"; + pos++; } else { - var result13 = null; - if (reportMatchFailures) { + result6 = null; + if (reportFailures === 0) { matchFailed("\")\""); } } - if (result13 !== null) { - var result5 = [result7, result8, result9, result10, result11, result12, result13]; + if (result6 !== null) { + result0 = [result0, result1, result2, result3, result4, result5, result6]; } else { - var result5 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } } else { - var result5 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } } else { - var result5 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } } else { - var result5 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } } else { - var result5 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } } else { - var result5 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } } else { - var result5 = null; - pos = savedPos2; - } - var result6 = result5 !== null - ? (function(type, value) { value.type = type; return value; })(result5[0], result5[4]) - : null; - if (result6 !== null) { - var result4 = result6; - } else { - var result4 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - if (result4 !== null) { - var result0 = result4; - } else { - var savedPos0 = pos; - var result2 = parse_type(); - var result3 = result2 !== null - ? (function(type) { return {type: type, value: one, exists: noop, fields: noop}; })(result2) - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, type, value) { value.type = type; return value; })(pos0, result0[0], result0[4]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + result0 = parse_type(); + if (result0 !== null) { + result0 = (function(offset, type) { return {type: type, value: one, exists: noop, fields: noop}; })(pos0, result0); + } + if (result0 === null) { + pos = pos0; } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_event_additive_expression() { - var cacheKey = 'event_additive_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_event_multiplicative_expression(); - if (result3 !== null) { - var result4 = []; - var savedPos2 = pos; - var result6 = parse__(); - if (result6 !== null) { - var result7 = parse_additive_operator(); - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - var result9 = parse_event_additive_expression(); - if (result9 !== null) { - var result5 = [result6, result7, result8, result9]; + var result0, result1, result2, result3, result4, result5; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + result0 = parse_event_multiplicative_expression(); + if (result0 !== null) { + result1 = []; + pos2 = pos; + result2 = parse__(); + if (result2 !== null) { + result3 = parse_additive_operator(); + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_event_additive_expression(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; - } - while (result5 !== null) { - result4.push(result5); - var savedPos2 = pos; - var result6 = parse__(); - if (result6 !== null) { - var result7 = parse_additive_operator(); - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - var result9 = parse_event_additive_expression(); - if (result9 !== null) { - var result5 = [result6, result7, result8, result9]; + result2 = null; + pos = pos2; + } + while (result2 !== null) { + result1.push(result2); + pos2 = pos; + result2 = parse__(); + if (result2 !== null) { + result3 = parse_additive_operator(); + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_event_additive_expression(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(head, tail) { return compoundValue(head, tail); })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, head, tail) { return compoundValue(head, tail); })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_event_multiplicative_expression() { - var cacheKey = 'event_multiplicative_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_event_unary_expression(); - if (result3 !== null) { - var result4 = []; - var savedPos2 = pos; - var result6 = parse__(); - if (result6 !== null) { - var result7 = parse_multiplicative_operator(); - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - var result9 = parse_event_multiplicative_expression(); - if (result9 !== null) { - var result5 = [result6, result7, result8, result9]; + var result0, result1, result2, result3, result4, result5; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + result0 = parse_event_unary_expression(); + if (result0 !== null) { + result1 = []; + pos2 = pos; + result2 = parse__(); + if (result2 !== null) { + result3 = parse_multiplicative_operator(); + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_event_multiplicative_expression(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; - } - while (result5 !== null) { - result4.push(result5); - var savedPos2 = pos; - var result6 = parse__(); - if (result6 !== null) { - var result7 = parse_multiplicative_operator(); - if (result7 !== null) { - var result8 = parse__(); - if (result8 !== null) { - var result9 = parse_event_multiplicative_expression(); - if (result9 !== null) { - var result5 = [result6, result7, result8, result9]; + result2 = null; + pos = pos2; + } + while (result2 !== null) { + result1.push(result2); + pos2 = pos; + result2 = parse__(); + if (result2 !== null) { + result3 = parse_multiplicative_operator(); + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_event_multiplicative_expression(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } else { - var result5 = null; - pos = savedPos2; + result2 = null; + pos = pos2; } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(head, tail) { return compoundValue(head, tail); })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, head, tail) { return compoundValue(head, tail); })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_event_unary_expression() { - var cacheKey = 'event_unary_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var result7 = parse_event_primary_expression(); - if (result7 !== null) { - var result0 = result7; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "-") { - var result4 = "-"; - pos += 1; + var result0, result1, result2; + var pos0, pos1; + + result0 = parse_event_primary_expression(); + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 45) { + result0 = "-"; + pos++; } else { - var result4 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"-\""); } } - if (result4 !== null) { - var result5 = parse__(); - if (result5 !== null) { - var result6 = parse_event_unary_expression(); - if (result6 !== null) { - var result2 = [result4, result5, result6]; + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + result2 = parse_event_unary_expression(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; - } - var result3 = result2 !== null - ? (function(unary) { return {value: function(o) { return -unary.value(o); }, exists: unary.exists, fields: unary.fields}; })(result2[2]) - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, unary) { return {value: function(o) { return -unary.value(o); }, exists: unary.exists, fields: unary.fields}; })(pos0, result0[2]); + } + if (result0 === null) { + pos = pos0; } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_event_primary_expression() { - var cacheKey = 'event_primary_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var result12 = parse_event_member_expression(); - if (result12 !== null) { - var result0 = result12; - } else { - var savedPos2 = pos; - var result10 = parse_number(); - var result11 = result10 !== null - ? (function(number) { return {value: function() { return number; }, exists: noop, fields: noop}; })(result10) - : null; - if (result11 !== null) { - var result9 = result11; - } else { - var result9 = null; - pos = savedPos2; - } - if (result9 !== null) { - var result0 = result9; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "(") { - var result4 = "("; - pos += 1; + var result0, result1, result2, result3, result4; + var pos0, pos1; + + result0 = parse_event_member_expression(); + if (result0 === null) { + pos0 = pos; + result0 = parse_number(); + if (result0 !== null) { + result0 = (function(offset, number) { return {value: function() { return number; }, exists: noop, fields: noop}; })(pos0, result0); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 40) { + result0 = "("; + pos++; } else { - var result4 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"(\""); } } - if (result4 !== null) { - var result5 = parse__(); - if (result5 !== null) { - var result6 = parse_event_additive_expression(); - if (result6 !== null) { - var result7 = parse__(); - if (result7 !== null) { - if (input.substr(pos, 1) === ")") { - var result8 = ")"; - pos += 1; + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + result2 = parse_event_additive_expression(); + if (result2 !== null) { + result3 = parse__(); + if (result3 !== null) { + if (input.charCodeAt(pos) === 41) { + result4 = ")"; + pos++; } else { - var result8 = null; - if (reportMatchFailures) { + result4 = null; + if (reportFailures === 0) { matchFailed("\")\""); } } - if (result8 !== null) { - var result2 = [result4, result5, result6, result7, result8]; + if (result4 !== null) { + result0 = [result0, result1, result2, result3, result4]; } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } - var result3 = result2 !== null - ? (function(expression) { return expression; })(result2[2]) - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, expression) { return expression; })(pos0, result0[2]); } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; + if (result0 === null) { + pos = pos0; + } + } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_event_member_expression() { - var cacheKey = 'event_member_expression@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_identifier(); - if (result3 !== null) { - var result4 = []; - var savedPos4 = pos; - var savedPos5 = pos; - var result16 = parse__(); - if (result16 !== null) { - if (input.substr(pos, 1) === "[") { - var result17 = "["; - pos += 1; + var result0, result1, result2, result3, result4, result5, result6, result7; + var pos0, pos1, pos2, pos3; + + pos0 = pos; + pos1 = pos; + result0 = parse_identifier(); + if (result0 !== null) { + result1 = []; + pos2 = pos; + pos3 = pos; + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 91) { + result3 = "["; + pos++; } else { - var result17 = null; - if (reportMatchFailures) { + result3 = null; + if (reportFailures === 0) { matchFailed("\"[\""); } } - if (result17 !== null) { - var result18 = parse__(); - if (result18 !== null) { - var result19 = parse_number(); - if (result19 !== null) { - var result20 = parse__(); - if (result20 !== null) { - if (input.substr(pos, 1) === "]") { - var result21 = "]"; - pos += 1; + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_number(); + if (result5 !== null) { + result6 = parse__(); + if (result6 !== null) { + if (input.charCodeAt(pos) === 93) { + result7 = "]"; + pos++; } else { - var result21 = null; - if (reportMatchFailures) { + result7 = null; + if (reportFailures === 0) { matchFailed("\"]\""); } } - if (result21 !== null) { - var result14 = [result16, result17, result18, result19, result20, result21]; + if (result7 !== null) { + result2 = [result2, result3, result4, result5, result6, result7]; } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; - } - var result15 = result14 !== null - ? (function(name) { return arrayAccessor(name); })(result14[3]) - : null; - if (result15 !== null) { - var result13 = result15; - } else { - var result13 = null; - pos = savedPos4; + result2 = null; + pos = pos3; } - if (result13 !== null) { - var result5 = result13; - } else { - var savedPos2 = pos; - var savedPos3 = pos; - var result9 = parse__(); - if (result9 !== null) { - if (input.substr(pos, 1) === ".") { - var result10 = "."; - pos += 1; + if (result2 !== null) { + result2 = (function(offset, name) { return arrayAccessor(name); })(pos2, result2[3]); + } + if (result2 === null) { + pos = pos2; + } + if (result2 === null) { + pos2 = pos; + pos3 = pos; + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 46) { + result3 = "."; + pos++; } else { - var result10 = null; - if (reportMatchFailures) { + result3 = null; + if (reportFailures === 0) { matchFailed("\".\""); } } - if (result10 !== null) { - var result11 = parse__(); - if (result11 !== null) { - var result12 = parse_identifier(); - if (result12 !== null) { - var result7 = [result9, result10, result11, result12]; + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_identifier(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } - var result8 = result7 !== null - ? (function(name) { return objectAccessor(name); })(result7[3]) - : null; - if (result8 !== null) { - var result6 = result8; - } else { - var result6 = null; - pos = savedPos2; + if (result2 !== null) { + result2 = (function(offset, name) { return objectAccessor(name); })(pos2, result2[3]); } - if (result6 !== null) { - var result5 = result6; - } else { - var result5 = null;; - }; - } - while (result5 !== null) { - result4.push(result5); - var savedPos4 = pos; - var savedPos5 = pos; - var result16 = parse__(); - if (result16 !== null) { - if (input.substr(pos, 1) === "[") { - var result17 = "["; - pos += 1; + if (result2 === null) { + pos = pos2; + } + } + while (result2 !== null) { + result1.push(result2); + pos2 = pos; + pos3 = pos; + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 91) { + result3 = "["; + pos++; } else { - var result17 = null; - if (reportMatchFailures) { + result3 = null; + if (reportFailures === 0) { matchFailed("\"[\""); } } - if (result17 !== null) { - var result18 = parse__(); - if (result18 !== null) { - var result19 = parse_number(); - if (result19 !== null) { - var result20 = parse__(); - if (result20 !== null) { - if (input.substr(pos, 1) === "]") { - var result21 = "]"; - pos += 1; + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_number(); + if (result5 !== null) { + result6 = parse__(); + if (result6 !== null) { + if (input.charCodeAt(pos) === 93) { + result7 = "]"; + pos++; } else { - var result21 = null; - if (reportMatchFailures) { + result7 = null; + if (reportFailures === 0) { matchFailed("\"]\""); } } - if (result21 !== null) { - var result14 = [result16, result17, result18, result19, result20, result21]; + if (result7 !== null) { + result2 = [result2, result3, result4, result5, result6, result7]; } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } } else { - var result14 = null; - pos = savedPos5; + result2 = null; + pos = pos3; } - var result15 = result14 !== null - ? (function(name) { return arrayAccessor(name); })(result14[3]) - : null; - if (result15 !== null) { - var result13 = result15; - } else { - var result13 = null; - pos = savedPos4; + if (result2 !== null) { + result2 = (function(offset, name) { return arrayAccessor(name); })(pos2, result2[3]); } - if (result13 !== null) { - var result5 = result13; - } else { - var savedPos2 = pos; - var savedPos3 = pos; - var result9 = parse__(); - if (result9 !== null) { - if (input.substr(pos, 1) === ".") { - var result10 = "."; - pos += 1; + if (result2 === null) { + pos = pos2; + } + if (result2 === null) { + pos2 = pos; + pos3 = pos; + result2 = parse__(); + if (result2 !== null) { + if (input.charCodeAt(pos) === 46) { + result3 = "."; + pos++; } else { - var result10 = null; - if (reportMatchFailures) { + result3 = null; + if (reportFailures === 0) { matchFailed("\".\""); } } - if (result10 !== null) { - var result11 = parse__(); - if (result11 !== null) { - var result12 = parse_identifier(); - if (result12 !== null) { - var result7 = [result9, result10, result11, result12]; + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + result5 = parse_identifier(); + if (result5 !== null) { + result2 = [result2, result3, result4, result5]; } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } } else { - var result7 = null; - pos = savedPos3; + result2 = null; + pos = pos3; } - var result8 = result7 !== null - ? (function(name) { return objectAccessor(name); })(result7[3]) - : null; - if (result8 !== null) { - var result6 = result8; - } else { - var result6 = null; - pos = savedPos2; + if (result2 !== null) { + result2 = (function(offset, name) { return objectAccessor(name); })(pos2, result2[3]); + } + if (result2 === null) { + pos = pos2; } - if (result6 !== null) { - var result5 = result6; - } else { - var result5 = null;; - }; } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(head, tail) { return member(head, tail); })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, head, tail) { return member(head, tail); })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_additive_operator() { - var cacheKey = 'additive_operator@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; + var pos0; - - var savedPos1 = pos; - if (input.substr(pos, 1) === "+") { - var result5 = "+"; - pos += 1; + pos0 = pos; + if (input.charCodeAt(pos) === 43) { + result0 = "+"; + pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"+\""); } } - var result6 = result5 !== null - ? (function() { return add; })() - : null; - if (result6 !== null) { - var result4 = result6; - } else { - var result4 = null; - pos = savedPos1; + if (result0 !== null) { + result0 = (function(offset) { return add; })(pos0); } - if (result4 !== null) { - var result0 = result4; - } else { - var savedPos0 = pos; - if (input.substr(pos, 1) === "-") { - var result2 = "-"; - pos += 1; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + if (input.charCodeAt(pos) === 45) { + result0 = "-"; + pos++; } else { - var result2 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"-\""); } } - var result3 = result2 !== null - ? (function() { return subtract; })() - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return subtract; })(pos0); + } + if (result0 === null) { + pos = pos0; } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_multiplicative_operator() { - var cacheKey = 'multiplicative_operator@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0; + var pos0; - var savedPos1 = pos; - if (input.substr(pos, 1) === "*") { - var result5 = "*"; - pos += 1; + pos0 = pos; + if (input.charCodeAt(pos) === 42) { + result0 = "*"; + pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"*\""); } } - var result6 = result5 !== null - ? (function() { return multiply; })() - : null; - if (result6 !== null) { - var result4 = result6; - } else { - var result4 = null; - pos = savedPos1; + if (result0 !== null) { + result0 = (function(offset) { return multiply; })(pos0); } - if (result4 !== null) { - var result0 = result4; - } else { - var savedPos0 = pos; - if (input.substr(pos, 1) === "/") { - var result2 = "/"; - pos += 1; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + if (input.charCodeAt(pos) === 47) { + result0 = "/"; + pos++; } else { - var result2 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"/\""); } } - var result3 = result2 !== null - ? (function() { return divide; })() - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return divide; })(pos0); + } + if (result0 === null) { + pos = pos0; } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_filter_operator() { - var cacheKey = 'filter_operator@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; + var pos0; - - var savedPos7 = pos; + pos0 = pos; if (input.substr(pos, 2) === "eq") { - var result23 = "eq"; + result0 = "eq"; pos += 2; } else { - var result23 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"eq\""); } } - var result24 = result23 !== null - ? (function() { return filterEqual; })() - : null; - if (result24 !== null) { - var result22 = result24; - } else { - var result22 = null; - pos = savedPos7; + if (result0 !== null) { + result0 = (function(offset) { return filterEqual; })(pos0); } - if (result22 !== null) { - var result0 = result22; - } else { - var savedPos6 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 2) === "gt") { - var result20 = "gt"; + result0 = "gt"; pos += 2; } else { - var result20 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"gt\""); } } - var result21 = result20 !== null - ? (function() { return filterGreater; })() - : null; - if (result21 !== null) { - var result19 = result21; - } else { - var result19 = null; - pos = savedPos6; + if (result0 !== null) { + result0 = (function(offset) { return filterGreater; })(pos0); } - if (result19 !== null) { - var result0 = result19; - } else { - var savedPos5 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 2) === "ge") { - var result17 = "ge"; + result0 = "ge"; pos += 2; } else { - var result17 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"ge\""); } } - var result18 = result17 !== null - ? (function() { return filterGreaterOrEqual; })() - : null; - if (result18 !== null) { - var result16 = result18; - } else { - var result16 = null; - pos = savedPos5; + if (result0 !== null) { + result0 = (function(offset) { return filterGreaterOrEqual; })(pos0); } - if (result16 !== null) { - var result0 = result16; - } else { - var savedPos4 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 2) === "lt") { - var result14 = "lt"; + result0 = "lt"; pos += 2; } else { - var result14 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"lt\""); } } - var result15 = result14 !== null - ? (function() { return filterLess; })() - : null; - if (result15 !== null) { - var result13 = result15; - } else { - var result13 = null; - pos = savedPos4; + if (result0 !== null) { + result0 = (function(offset) { return filterLess; })(pos0); } - if (result13 !== null) { - var result0 = result13; - } else { - var savedPos3 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 2) === "le") { - var result11 = "le"; + result0 = "le"; pos += 2; } else { - var result11 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"le\""); } } - var result12 = result11 !== null - ? (function() { return filterLessOrEqual; })() - : null; - if (result12 !== null) { - var result10 = result12; - } else { - var result10 = null; - pos = savedPos3; + if (result0 !== null) { + result0 = (function(offset) { return filterLessOrEqual; })(pos0); } - if (result10 !== null) { - var result0 = result10; - } else { - var savedPos2 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 2) === "ne") { - var result8 = "ne"; + result0 = "ne"; pos += 2; } else { - var result8 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"ne\""); } } - var result9 = result8 !== null - ? (function() { return filterNotEqual; })() - : null; - if (result9 !== null) { - var result7 = result9; - } else { - var result7 = null; - pos = savedPos2; + if (result0 !== null) { + result0 = (function(offset) { return filterNotEqual; })(pos0); } - if (result7 !== null) { - var result0 = result7; - } else { - var savedPos1 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 2) === "re") { - var result5 = "re"; + result0 = "re"; pos += 2; } else { - var result5 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"re\""); } } - var result6 = result5 !== null - ? (function() { return filterRegularExpression; })() - : null; - if (result6 !== null) { - var result4 = result6; - } else { - var result4 = null; - pos = savedPos1; + if (result0 !== null) { + result0 = (function(offset) { return filterRegularExpression; })(pos0); } - if (result4 !== null) { - var result0 = result4; - } else { - var savedPos0 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 2) === "in") { - var result2 = "in"; + result0 = "in"; pos += 2; } else { - var result2 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"in\""); } } - var result3 = result2 !== null - ? (function() { return filterIn; })() - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return filterIn; })(pos0); } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; - }; - }; - }; - }; + if (result0 === null) { + pos = pos0; + } + } + } + } + } + } + } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_reduce() { - var cacheKey = 'reduce@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0; if (input.substr(pos, 3) === "sum") { - var result5 = "sum"; + result0 = "sum"; pos += 3; } else { - var result5 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"sum\""); } } - if (result5 !== null) { - var result0 = result5; - } else { + if (result0 === null) { if (input.substr(pos, 3) === "min") { - var result4 = "min"; + result0 = "min"; pos += 3; } else { - var result4 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"min\""); } } - if (result4 !== null) { - var result0 = result4; - } else { + if (result0 === null) { if (input.substr(pos, 3) === "max") { - var result3 = "max"; + result0 = "max"; pos += 3; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"max\""); } } - if (result3 !== null) { - var result0 = result3; - } else { + if (result0 === null) { if (input.substr(pos, 8) === "distinct") { - var result2 = "distinct"; + result0 = "distinct"; pos += 8; } else { - var result2 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"distinct\""); } } - if (result2 !== null) { - var result0 = result2; - } else { + if (result0 === null) { if (input.substr(pos, 6) === "median") { - var result1 = "median"; + result0 = "median"; pos += 6; } else { - var result1 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"median\""); } } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; - }; + } + } + } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_type() { - var cacheKey = 'type@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0, result1, result2; + var pos0, pos1; - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos).match(/^[a-z]/) !== null) { - var result3 = input.charAt(pos); + pos0 = pos; + pos1 = pos; + if (/^[a-z]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[a-z]"); } } - if (result3 !== null) { - if (input.substr(pos).match(/^[a-zA-Z0-9_]/) !== null) { - var result5 = input.charAt(pos); + if (result0 !== null) { + if (/^[a-zA-Z0-9_]/.test(input.charAt(pos))) { + result2 = input.charAt(pos); pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("[a-zA-Z0-9_]"); } } - if (result5 !== null) { - var result4 = []; - while (result5 !== null) { - result4.push(result5); - if (input.substr(pos).match(/^[a-zA-Z0-9_]/) !== null) { - var result5 = input.charAt(pos); + if (result2 !== null) { + result1 = []; + while (result2 !== null) { + result1.push(result2); + if (/^[a-zA-Z0-9_]/.test(input.charAt(pos))) { + result2 = input.charAt(pos); pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("[a-zA-Z0-9_]"); } } } } else { - var result4 = null; + result1 = null; } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(first, rest) { return first + rest.join(""); })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, first, rest) { return first + rest.join(""); })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_identifier() { - var cacheKey = 'identifier@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos).match(/^[a-zA-Z_]/) !== null) { - var result3 = input.charAt(pos); + pos0 = pos; + pos1 = pos; + if (/^[a-zA-Z_]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[a-zA-Z_]"); } } - if (result3 !== null) { - var result4 = []; - if (input.substr(pos).match(/^[a-zA-Z0-9_$]/) !== null) { - var result5 = input.charAt(pos); + if (result0 !== null) { + result1 = []; + if (/^[a-zA-Z0-9_$]/.test(input.charAt(pos))) { + result2 = input.charAt(pos); pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("[a-zA-Z0-9_$]"); } } - while (result5 !== null) { - result4.push(result5); - if (input.substr(pos).match(/^[a-zA-Z0-9_$]/) !== null) { - var result5 = input.charAt(pos); + while (result2 !== null) { + result1.push(result2); + if (/^[a-zA-Z0-9_$]/.test(input.charAt(pos))) { + result2 = input.charAt(pos); pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("[a-zA-Z0-9_$]"); } } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(first, rest) { return first + rest.join(""); })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, first, rest) { return first + rest.join(""); })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_literal() { - var cacheKey = 'literal@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var result9 = parse_array_literal(); - if (result9 !== null) { - var result0 = result9; - } else { - var result8 = parse_string(); - if (result8 !== null) { - var result0 = result8; - } else { - var result7 = parse_number(); - if (result7 !== null) { - var result0 = result7; - } else { - var savedPos1 = pos; + var result0; + var pos0; + + result0 = parse_array_literal(); + if (result0 === null) { + result0 = parse_string(); + if (result0 === null) { + result0 = parse_number(); + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 4) === "true") { - var result5 = "true"; + result0 = "true"; pos += 4; } else { - var result5 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"true\""); } } - var result6 = result5 !== null - ? (function() { return true; })() - : null; - if (result6 !== null) { - var result4 = result6; - } else { - var result4 = null; - pos = savedPos1; + if (result0 !== null) { + result0 = (function(offset) { return true; })(pos0); } - if (result4 !== null) { - var result0 = result4; - } else { - var savedPos0 = pos; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; if (input.substr(pos, 5) === "false") { - var result2 = "false"; + result0 = "false"; pos += 5; } else { - var result2 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"false\""); } } - var result3 = result2 !== null - ? (function() { return false; })() - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset) { return false; })(pos0); } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; - }; + if (result0 === null) { + pos = pos0; + } + } + } + } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_array_literal() { - var cacheKey = 'array_literal@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0, result1, result2, result3, result4, result5, result6, result7; + var pos0, pos1, pos2; - var savedPos2 = pos; - var savedPos3 = pos; - if (input.substr(pos, 1) === "[") { - var result10 = "["; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 91) { + result0 = "["; + pos++; } else { - var result10 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"[\""); } } - if (result10 !== null) { - var result11 = parse__(); - if (result11 !== null) { - var result12 = parse_literal(); - if (result12 !== null) { - var result13 = []; - var savedPos4 = pos; - var result17 = parse__(); - if (result17 !== null) { - if (input.substr(pos, 1) === ",") { - var result18 = ","; - pos += 1; + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + result2 = parse_literal(); + if (result2 !== null) { + result3 = []; + pos2 = pos; + result4 = parse__(); + if (result4 !== null) { + if (input.charCodeAt(pos) === 44) { + result5 = ","; + pos++; } else { - var result18 = null; - if (reportMatchFailures) { + result5 = null; + if (reportFailures === 0) { matchFailed("\",\""); } } - if (result18 !== null) { - var result19 = parse__(); - if (result19 !== null) { - var result20 = parse_literal(); - if (result20 !== null) { - var result16 = [result17, result18, result19, result20]; + if (result5 !== null) { + result6 = parse__(); + if (result6 !== null) { + result7 = parse_literal(); + if (result7 !== null) { + result4 = [result4, result5, result6, result7]; } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } - while (result16 !== null) { - result13.push(result16); - var savedPos4 = pos; - var result17 = parse__(); - if (result17 !== null) { - if (input.substr(pos, 1) === ",") { - var result18 = ","; - pos += 1; + while (result4 !== null) { + result3.push(result4); + pos2 = pos; + result4 = parse__(); + if (result4 !== null) { + if (input.charCodeAt(pos) === 44) { + result5 = ","; + pos++; } else { - var result18 = null; - if (reportMatchFailures) { + result5 = null; + if (reportFailures === 0) { matchFailed("\",\""); } } - if (result18 !== null) { - var result19 = parse__(); - if (result19 !== null) { - var result20 = parse_literal(); - if (result20 !== null) { - var result16 = [result17, result18, result19, result20]; + if (result5 !== null) { + result6 = parse__(); + if (result6 !== null) { + result7 = parse_literal(); + if (result7 !== null) { + result4 = [result4, result5, result6, result7]; } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } } else { - var result16 = null; - pos = savedPos4; + result4 = null; + pos = pos2; } } - if (result13 !== null) { - var result14 = parse__(); - if (result14 !== null) { - if (input.substr(pos, 1) === "]") { - var result15 = "]"; - pos += 1; + if (result3 !== null) { + result4 = parse__(); + if (result4 !== null) { + if (input.charCodeAt(pos) === 93) { + result5 = "]"; + pos++; } else { - var result15 = null; - if (reportMatchFailures) { + result5 = null; + if (reportFailures === 0) { matchFailed("\"]\""); } } - if (result15 !== null) { - var result8 = [result10, result11, result12, result13, result14, result15]; + if (result5 !== null) { + result0 = [result0, result1, result2, result3, result4, result5]; } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result8 = null; - pos = savedPos3; - } - var result9 = result8 !== null - ? (function(first, rest) { return [first].concat(rest.map(function(d) { return d[3]; })); })(result8[2], result8[3]) - : null; - if (result9 !== null) { - var result7 = result9; - } else { - var result7 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } - if (result7 !== null) { - var result0 = result7; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "[") { - var result4 = "["; - pos += 1; + if (result0 !== null) { + result0 = (function(offset, first, rest) { return [first].concat(rest.map(function(d) { return d[3]; })); })(pos0, result0[2], result0[3]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 91) { + result0 = "["; + pos++; } else { - var result4 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"[\""); } } - if (result4 !== null) { - var result5 = parse__(); - if (result5 !== null) { - if (input.substr(pos, 1) === "]") { - var result6 = "]"; - pos += 1; + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + if (input.charCodeAt(pos) === 93) { + result2 = "]"; + pos++; } else { - var result6 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"]\""); } } - if (result6 !== null) { - var result2 = [result4, result5, result6]; + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; - } - var result3 = result2 !== null - ? (function() { return []; })() - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset) { return []; })(pos0); + } + if (result0 === null) { + pos = pos0; } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_string() { - var cacheKey = 'string@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - var savedReportMatchFailures = reportMatchFailures; - reportMatchFailures = false; - var savedPos2 = pos; - var savedPos3 = pos; - if (input.substr(pos, 1) === "\"") { - var result11 = "\""; - pos += 1; + var result0, result1, result2; + var pos0, pos1; + + reportFailures++; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 34) { + result0 = "\""; + pos++; } else { - var result11 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\"\""); } } - if (result11 !== null) { - var result12 = []; - var result14 = parse_double_string_char(); - while (result14 !== null) { - result12.push(result14); - var result14 = parse_double_string_char(); + if (result0 !== null) { + result1 = []; + result2 = parse_double_string_char(); + while (result2 !== null) { + result1.push(result2); + result2 = parse_double_string_char(); } - if (result12 !== null) { - if (input.substr(pos, 1) === "\"") { - var result13 = "\""; - pos += 1; + if (result1 !== null) { + if (input.charCodeAt(pos) === 34) { + result2 = "\""; + pos++; } else { - var result13 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"\\\"\""); } } - if (result13 !== null) { - var result9 = [result11, result12, result13]; + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result9 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result9 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result9 = null; - pos = savedPos3; - } - var result10 = result9 !== null - ? (function(chars) { return chars.join(""); })(result9[1]) - : null; - if (result10 !== null) { - var result8 = result10; - } else { - var result8 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } - if (result8 !== null) { - var result0 = result8; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "'") { - var result4 = "'"; - pos += 1; + if (result0 !== null) { + result0 = (function(offset, chars) { return chars.join(""); })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 39) { + result0 = "'"; + pos++; } else { - var result4 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"'\""); } } - if (result4 !== null) { - var result5 = []; - var result7 = parse_single_string_char(); - while (result7 !== null) { - result5.push(result7); - var result7 = parse_single_string_char(); + if (result0 !== null) { + result1 = []; + result2 = parse_single_string_char(); + while (result2 !== null) { + result1.push(result2); + result2 = parse_single_string_char(); } - if (result5 !== null) { - if (input.substr(pos, 1) === "'") { - var result6 = "'"; - pos += 1; + if (result1 !== null) { + if (input.charCodeAt(pos) === 39) { + result2 = "'"; + pos++; } else { - var result6 = null; - if (reportMatchFailures) { + result2 = null; + if (reportFailures === 0) { matchFailed("\"'\""); } } - if (result6 !== null) { - var result2 = [result4, result5, result6]; + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; - } - var result3 = result2 !== null - ? (function(chars) { return chars.join(""); })(result2[1]) - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, chars) { return chars.join(""); })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; } - reportMatchFailures = savedReportMatchFailures; - if (reportMatchFailures && result0 === null) { + reportFailures--; + if (reportFailures === 0 && result0 === null) { matchFailed("string"); } - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_double_string_char() { - var cacheKey = 'double_string_char@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos2 = pos; - var savedPos3 = pos; - var savedPos4 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - if (input.substr(pos, 1) === "\"") { - var result13 = "\""; - pos += 1; + var result0, result1; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + pos2 = pos; + reportFailures++; + if (input.charCodeAt(pos) === 34) { + result0 = "\""; + pos++; } else { - var result13 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\"\""); } } - if (result13 !== null) { - var result11 = result13; - } else { - if (input.substr(pos, 1) === "\\") { - var result12 = "\\"; - pos += 1; + if (result0 === null) { + if (input.charCodeAt(pos) === 92) { + result0 = "\\"; + pos++; } else { - var result12 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\\""); } } - if (result12 !== null) { - var result11 = result12; - } else { - var result11 = null;; - }; } - reportMatchFailures = savedReportMatchFailuresVar0; - if (result11 === null) { - var result9 = ''; + reportFailures--; + if (result0 === null) { + result0 = ""; } else { - var result9 = null; - pos = savedPos4; + result0 = null; + pos = pos2; } - if (result9 !== null) { + if (result0 !== null) { if (input.length > pos) { - var result10 = input.charAt(pos); + result1 = input.charAt(pos); pos++; } else { - var result10 = null; - if (reportMatchFailures) { - matchFailed('any character'); + result1 = null; + if (reportFailures === 0) { + matchFailed("any character"); } } - if (result10 !== null) { - var result7 = [result9, result10]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result7 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result7 = null; - pos = savedPos3; - } - var result8 = result7 !== null - ? (function(char_) { return char_; })(result7[1]) - : null; - if (result8 !== null) { - var result6 = result8; - } else { - var result6 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } - if (result6 !== null) { - var result0 = result6; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "\\") { - var result4 = "\\"; - pos += 1; + if (result0 !== null) { + result0 = (function(offset, char_) { return char_; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 92) { + result0 = "\\"; + pos++; } else { - var result4 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\\""); } } - if (result4 !== null) { - var result5 = parse_escape_sequence(); - if (result5 !== null) { - var result2 = [result4, result5]; + if (result0 !== null) { + result1 = parse_escape_sequence(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; - } - var result3 = result2 !== null - ? (function(sequence) { return sequence; })(result2[1]) - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, sequence) { return sequence; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_single_string_char() { - var cacheKey = 'single_string_char@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos2 = pos; - var savedPos3 = pos; - var savedPos4 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - if (input.substr(pos, 1) === "'") { - var result13 = "'"; - pos += 1; + var result0, result1; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + pos2 = pos; + reportFailures++; + if (input.charCodeAt(pos) === 39) { + result0 = "'"; + pos++; } else { - var result13 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"'\""); } } - if (result13 !== null) { - var result11 = result13; - } else { - if (input.substr(pos, 1) === "\\") { - var result12 = "\\"; - pos += 1; + if (result0 === null) { + if (input.charCodeAt(pos) === 92) { + result0 = "\\"; + pos++; } else { - var result12 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\\""); } } - if (result12 !== null) { - var result11 = result12; - } else { - var result11 = null;; - }; } - reportMatchFailures = savedReportMatchFailuresVar0; - if (result11 === null) { - var result9 = ''; + reportFailures--; + if (result0 === null) { + result0 = ""; } else { - var result9 = null; - pos = savedPos4; + result0 = null; + pos = pos2; } - if (result9 !== null) { + if (result0 !== null) { if (input.length > pos) { - var result10 = input.charAt(pos); + result1 = input.charAt(pos); pos++; } else { - var result10 = null; - if (reportMatchFailures) { - matchFailed('any character'); + result1 = null; + if (reportFailures === 0) { + matchFailed("any character"); } } - if (result10 !== null) { - var result7 = [result9, result10]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result7 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result7 = null; - pos = savedPos3; - } - var result8 = result7 !== null - ? (function(char_) { return char_; })(result7[1]) - : null; - if (result8 !== null) { - var result6 = result8; - } else { - var result6 = null; - pos = savedPos2; + result0 = null; + pos = pos1; } - if (result6 !== null) { - var result0 = result6; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "\\") { - var result4 = "\\"; - pos += 1; + if (result0 !== null) { + result0 = (function(offset, char_) { return char_; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 92) { + result0 = "\\"; + pos++; } else { - var result4 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"\\\\\""); } } - if (result4 !== null) { - var result5 = parse_escape_sequence(); - if (result5 !== null) { - var result2 = [result4, result5]; + if (result0 !== null) { + result1 = parse_escape_sequence(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result2 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result2 = null; - pos = savedPos1; - } - var result3 = result2 !== null - ? (function(sequence) { return sequence; })(result2[1]) - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, sequence) { return sequence; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_escape_sequence() { - var cacheKey = 'escape_sequence@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var result9 = parse_character_escape_sequence(); - if (result9 !== null) { - var result0 = result9; - } else { - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "0") { - var result6 = "0"; - pos += 1; + var result0, result1; + var pos0, pos1, pos2; + + result0 = parse_character_escape_sequence(); + if (result0 === null) { + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 48) { + result0 = "0"; + pos++; } else { - var result6 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"0\""); } } - if (result6 !== null) { - var savedPos2 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - var result8 = parse_digit(); - reportMatchFailures = savedReportMatchFailuresVar0; - if (result8 === null) { - var result7 = ''; + if (result0 !== null) { + pos2 = pos; + reportFailures++; + result1 = parse_digit(); + reportFailures--; + if (result1 === null) { + result1 = ""; } else { - var result7 = null; - pos = savedPos2; + result1 = null; + pos = pos2; } - if (result7 !== null) { - var result4 = [result6, result7]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result4 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result4 = null; - pos = savedPos1; - } - var result5 = result4 !== null - ? (function() { return "\0"; })() - : null; - if (result5 !== null) { - var result3 = result5; - } else { - var result3 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset) { return "\0"; })(pos0); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + result0 = parse_hex_escape_sequence(); + if (result0 === null) { + result0 = parse_unicode_escape_sequence(); + } } - if (result3 !== null) { - var result0 = result3; - } else { - var result2 = parse_hex_escape_sequence(); - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_unicode_escape_sequence(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_character_escape_sequence() { - var cacheKey = 'character_escape_sequence@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0; - var result2 = parse_single_escape_character(); - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_non_escape_character(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; + result0 = parse_single_escape_character(); + if (result0 === null) { + result0 = parse_non_escape_character(); } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_single_escape_character() { - var cacheKey = 'single_escape_character@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0; + var pos0; - var savedPos0 = pos; - if (input.substr(pos).match(/^['"\\bfnrtv]/) !== null) { - var result1 = input.charAt(pos); + pos0 = pos; + if (/^['"\\bfnrtv]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result1 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("['\"\\\\bfnrtv]"); } } - var result2 = result1 !== null - ? (function(char_) { return char_.replace("b", "\b").replace("f", "\f").replace("n", "\n").replace("r", "\r").replace("t", "\t").replace("v", "\x0B"); })(result1) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, char_) { return char_.replace("b", "\b").replace("f", "\f").replace("n", "\n").replace("r", "\r").replace("t", "\t").replace("v", "\x0B"); })(pos0, result0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_non_escape_character() { - var cacheKey = 'non_escape_character@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var savedPos2 = pos; - var savedReportMatchFailuresVar0 = reportMatchFailures; - reportMatchFailures = false; - var result5 = parse_escape_character(); - reportMatchFailures = savedReportMatchFailuresVar0; - if (result5 === null) { - var result3 = ''; - } else { - var result3 = null; - pos = savedPos2; - } - if (result3 !== null) { + var result0, result1; + var pos0, pos1, pos2; + + pos0 = pos; + pos1 = pos; + pos2 = pos; + reportFailures++; + result0 = parse_escape_character(); + reportFailures--; + if (result0 === null) { + result0 = ""; + } else { + result0 = null; + pos = pos2; + } + if (result0 !== null) { if (input.length > pos) { - var result4 = input.charAt(pos); + result1 = input.charAt(pos); pos++; } else { - var result4 = null; - if (reportMatchFailures) { - matchFailed('any character'); + result1 = null; + if (reportFailures === 0) { + matchFailed("any character"); } } - if (result4 !== null) { - var result1 = [result3, result4]; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(char_) { return char_; })(result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, char_) { return char_; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_escape_character() { - var cacheKey = 'escape_character@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var result4 = parse_single_escape_character(); - if (result4 !== null) { - var result0 = result4; - } else { - var result3 = parse_digit(); - if (result3 !== null) { - var result0 = result3; - } else { - if (input.substr(pos, 1) === "x") { - var result2 = "x"; - pos += 1; + var result0; + + result0 = parse_single_escape_character(); + if (result0 === null) { + result0 = parse_digit(); + if (result0 === null) { + if (input.charCodeAt(pos) === 120) { + result0 = "x"; + pos++; } else { - var result2 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"x\""); } } - if (result2 !== null) { - var result0 = result2; - } else { - if (input.substr(pos, 1) === "u") { - var result1 = "u"; - pos += 1; + if (result0 === null) { + if (input.charCodeAt(pos) === 117) { + result0 = "u"; + pos++; } else { - var result1 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"u\""); } } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; + } + } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_hex_escape_sequence() { - var cacheKey = 'hex_escape_sequence@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0, result1, result2; + var pos0, pos1; - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "x") { - var result3 = "x"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 120) { + result0 = "x"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"x\""); } } - if (result3 !== null) { - var result4 = parse_hex_digit(); - if (result4 !== null) { - var result5 = parse_hex_digit(); - if (result5 !== null) { - var result1 = [result3, result4, result5]; + if (result0 !== null) { + result1 = parse_hex_digit(); + if (result1 !== null) { + result2 = parse_hex_digit(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(h1, h2) { return String.fromCharCode(+("0x" + h1 + h2)); })(result1[1], result1[2]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, h1, h2) { return String.fromCharCode(+("0x" + h1 + h2)); })(pos0, result0[1], result0[2]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_unicode_escape_sequence() { - var cacheKey = 'unicode_escape_sequence@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1, result2, result3, result4; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === "u") { - var result3 = "u"; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 117) { + result0 = "u"; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"u\""); } } - if (result3 !== null) { - var result4 = parse_hex_digit(); - if (result4 !== null) { - var result5 = parse_hex_digit(); - if (result5 !== null) { - var result6 = parse_hex_digit(); - if (result6 !== null) { - var result7 = parse_hex_digit(); - if (result7 !== null) { - var result1 = [result3, result4, result5, result6, result7]; + if (result0 !== null) { + result1 = parse_hex_digit(); + if (result1 !== null) { + result2 = parse_hex_digit(); + if (result2 !== null) { + result3 = parse_hex_digit(); + if (result3 !== null) { + result4 = parse_hex_digit(); + if (result4 !== null) { + result0 = [result0, result1, result2, result3, result4]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(h1, h2, h3, h4) { return String.fromCharCode(+("0x" + h1 + h2 + h3 + h4)); })(result1[1], result1[2], result1[3], result1[4]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, h1, h2, h3, h4) { return String.fromCharCode(+("0x" + h1 + h2 + h3 + h4)); })(pos0, result0[1], result0[2], result0[3], result0[4]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_number() { - var cacheKey = 'number@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - var savedReportMatchFailures = reportMatchFailures; - reportMatchFailures = false; - var savedPos8 = pos; - var savedPos9 = pos; - if (input.substr(pos, 1) === "-") { - var result26 = "-"; - pos += 1; + var result0, result1, result2; + var pos0, pos1; + + reportFailures++; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 45) { + result0 = "-"; + pos++; } else { - var result26 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\"-\""); } } - if (result26 !== null) { - var result27 = parse__(); - if (result27 !== null) { - var result28 = parse_number(); - if (result28 !== null) { - var result24 = [result26, result27, result28]; + if (result0 !== null) { + result1 = parse__(); + if (result1 !== null) { + result2 = parse_number(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result24 = null; - pos = savedPos9; + result0 = null; + pos = pos1; } } else { - var result24 = null; - pos = savedPos9; + result0 = null; + pos = pos1; } } else { - var result24 = null; - pos = savedPos9; - } - var result25 = result24 !== null - ? (function(number) { return -number; })(result24[2]) - : null; - if (result25 !== null) { - var result23 = result25; - } else { - var result23 = null; - pos = savedPos8; + result0 = null; + pos = pos1; } - if (result23 !== null) { - var result0 = result23; - } else { - var savedPos6 = pos; - var savedPos7 = pos; - var result20 = parse_int(); - if (result20 !== null) { - var result21 = parse_frac(); - if (result21 !== null) { - var result22 = parse_exp(); - if (result22 !== null) { - var result18 = [result20, result21, result22]; + if (result0 !== null) { + result0 = (function(offset, number) { return -number; })(pos0, result0[2]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + result0 = parse_int(); + if (result0 !== null) { + result1 = parse_frac(); + if (result1 !== null) { + result2 = parse_exp(); + if (result2 !== null) { + result0 = [result0, result1, result2]; } else { - var result18 = null; - pos = savedPos7; + result0 = null; + pos = pos1; } } else { - var result18 = null; - pos = savedPos7; + result0 = null; + pos = pos1; } } else { - var result18 = null; - pos = savedPos7; - } - var result19 = result18 !== null - ? (function(int_, frac, exp) { return +(int_ + frac + exp); })(result18[0], result18[1], result18[2]) - : null; - if (result19 !== null) { - var result17 = result19; - } else { - var result17 = null; - pos = savedPos6; + result0 = null; + pos = pos1; } - if (result17 !== null) { - var result0 = result17; - } else { - var savedPos4 = pos; - var savedPos5 = pos; - var result15 = parse_int(); - if (result15 !== null) { - var result16 = parse_frac(); - if (result16 !== null) { - var result13 = [result15, result16]; + if (result0 !== null) { + result0 = (function(offset, int_, frac, exp) { return +(int_ + frac + exp); })(pos0, result0[0], result0[1], result0[2]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + result0 = parse_int(); + if (result0 !== null) { + result1 = parse_frac(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result13 = null; - pos = savedPos5; + result0 = null; + pos = pos1; } } else { - var result13 = null; - pos = savedPos5; + result0 = null; + pos = pos1; } - var result14 = result13 !== null - ? (function(int_, frac) { return +(int_ + frac); })(result13[0], result13[1]) - : null; - if (result14 !== null) { - var result12 = result14; - } else { - var result12 = null; - pos = savedPos4; + if (result0 !== null) { + result0 = (function(offset, int_, frac) { return +(int_ + frac); })(pos0, result0[0], result0[1]); } - if (result12 !== null) { - var result0 = result12; - } else { - var savedPos2 = pos; - var savedPos3 = pos; - var result10 = parse_int(); - if (result10 !== null) { - var result11 = parse_exp(); - if (result11 !== null) { - var result8 = [result10, result11]; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + pos1 = pos; + result0 = parse_int(); + if (result0 !== null) { + result1 = parse_exp(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } } else { - var result8 = null; - pos = savedPos3; + result0 = null; + pos = pos1; } - var result9 = result8 !== null - ? (function(int_, exp) { return +(int_ + exp); })(result8[0], result8[1]) - : null; - if (result9 !== null) { - var result7 = result9; - } else { - var result7 = null; - pos = savedPos2; + if (result0 !== null) { + result0 = (function(offset, int_, exp) { return +(int_ + exp); })(pos0, result0[0], result0[1]); } - if (result7 !== null) { - var result0 = result7; - } else { - var savedPos1 = pos; - var result5 = parse_frac(); - var result6 = result5 !== null - ? (function(frac) { return +frac; })(result5) - : null; - if (result6 !== null) { - var result4 = result6; - } else { - var result4 = null; - pos = savedPos1; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + result0 = parse_frac(); + if (result0 !== null) { + result0 = (function(offset, frac) { return +frac; })(pos0, result0); } - if (result4 !== null) { - var result0 = result4; - } else { - var savedPos0 = pos; - var result2 = parse_int(); - var result3 = result2 !== null - ? (function(int_) { return +int_; })(result2) - : null; - if (result3 !== null) { - var result1 = result3; - } else { - var result1 = null; - pos = savedPos0; + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + pos0 = pos; + result0 = parse_int(); + if (result0 !== null) { + result0 = (function(offset, int_) { return +int_; })(pos0, result0); } - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; - }; - }; - }; - }; + if (result0 === null) { + pos = pos0; + } + } + } + } + } } - reportMatchFailures = savedReportMatchFailures; - if (reportMatchFailures && result0 === null) { + reportFailures--; + if (reportFailures === 0 && result0 === null) { matchFailed("number"); } - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_int() { - var cacheKey = 'int@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result5 = parse_digit19(); - if (result5 !== null) { - var result6 = parse_digits(); - if (result6 !== null) { - var result3 = [result5, result6]; + var result0, result1; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse_digit19(); + if (result0 !== null) { + result1 = parse_digits(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result3 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result3 = null; - pos = savedPos1; - } - var result4 = result3 !== null - ? (function(digit19, digits) { return digit19 + digits; })(result3[0], result3[1]) - : null; - if (result4 !== null) { - var result2 = result4; - } else { - var result2 = null; - pos = savedPos0; + result0 = null; + pos = pos1; } - if (result2 !== null) { - var result0 = result2; - } else { - var result1 = parse_digit(); - if (result1 !== null) { - var result0 = result1; - } else { - var result0 = null;; - }; + if (result0 !== null) { + result0 = (function(offset, digit19, digits) { return digit19 + digits; })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; + } + if (result0 === null) { + result0 = parse_digit(); } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_frac() { - var cacheKey = 'frac@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; + var pos0, pos1; - - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos, 1) === ".") { - var result3 = "."; - pos += 1; + pos0 = pos; + pos1 = pos; + if (input.charCodeAt(pos) === 46) { + result0 = "."; + pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("\".\""); } } - if (result3 !== null) { - var result4 = parse_digits(); - if (result4 !== null) { - var result1 = [result3, result4]; + if (result0 !== null) { + result1 = parse_digits(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(digits) { return "." + digits; })(result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, digits) { return "." + digits; })(pos0, result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_exp() { - var cacheKey = 'exp@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - - - var savedPos0 = pos; - var savedPos1 = pos; - var result3 = parse_e(); - if (result3 !== null) { - var result4 = parse_digits(); - if (result4 !== null) { - var result1 = [result3, result4]; + var result0, result1; + var pos0, pos1; + + pos0 = pos; + pos1 = pos; + result0 = parse_e(); + if (result0 !== null) { + result1 = parse_digits(); + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(e, digits) { return e + digits; })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, e, digits) { return e + digits; })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_digits() { - var cacheKey = 'digits@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0, result1; + var pos0; - var savedPos0 = pos; - var result3 = parse_digit(); - if (result3 !== null) { - var result1 = []; - while (result3 !== null) { - result1.push(result3); - var result3 = parse_digit(); + pos0 = pos; + result1 = parse_digit(); + if (result1 !== null) { + result0 = []; + while (result1 !== null) { + result0.push(result1); + result1 = parse_digit(); } } else { - var result1 = null; + result0 = null; } - var result2 = result1 !== null - ? (function(digits) { return digits.join(""); })(result1) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + if (result0 !== null) { + result0 = (function(offset, digits) { return digits.join(""); })(pos0, result0); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_e() { - var cacheKey = 'e@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0, result1; + var pos0, pos1; - var savedPos0 = pos; - var savedPos1 = pos; - if (input.substr(pos).match(/^[eE]/) !== null) { - var result3 = input.charAt(pos); + pos0 = pos; + pos1 = pos; + if (/^[eE]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result3 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[eE]"); } } - if (result3 !== null) { - if (input.substr(pos).match(/^[+\-]/) !== null) { - var result5 = input.charAt(pos); + if (result0 !== null) { + if (/^[+\-]/.test(input.charAt(pos))) { + result1 = input.charAt(pos); pos++; } else { - var result5 = null; - if (reportMatchFailures) { + result1 = null; + if (reportFailures === 0) { matchFailed("[+\\-]"); } } - var result4 = result5 !== null ? result5 : ''; - if (result4 !== null) { - var result1 = [result3, result4]; + result1 = result1 !== null ? result1 : ""; + if (result1 !== null) { + result0 = [result0, result1]; } else { - var result1 = null; - pos = savedPos1; + result0 = null; + pos = pos1; } } else { - var result1 = null; - pos = savedPos1; - } - var result2 = result1 !== null - ? (function(e, sign) { return e + sign; })(result1[0], result1[1]) - : null; - if (result2 !== null) { - var result0 = result2; - } else { - var result0 = null; - pos = savedPos0; + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, e, sign) { return e + sign; })(pos0, result0[0], result0[1]); + } + if (result0 === null) { + pos = pos0; } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_digit() { - var cacheKey = 'digit@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0; - if (input.substr(pos).match(/^[0-9]/) !== null) { - var result0 = input.charAt(pos); + if (/^[0-9]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[0-9]"); } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_digit19() { - var cacheKey = 'digit19@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0; - - if (input.substr(pos).match(/^[1-9]/) !== null) { - var result0 = input.charAt(pos); + if (/^[1-9]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[1-9]"); } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_hex_digit() { - var cacheKey = 'hex_digit@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0; - if (input.substr(pos).match(/^[0-9a-fA-F]/) !== null) { - var result0 = input.charAt(pos); + if (/^[0-9a-fA-F]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { + result0 = null; + if (reportFailures === 0) { matchFailed("[0-9a-fA-F]"); } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse__() { - var cacheKey = '_@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } + var result0, result1; - var savedReportMatchFailures = reportMatchFailures; - reportMatchFailures = false; - var result0 = []; - var result1 = parse_whitespace(); + reportFailures++; + result0 = []; + result1 = parse_whitespace(); while (result1 !== null) { result0.push(result1); - var result1 = parse_whitespace(); + result1 = parse_whitespace(); } - reportMatchFailures = savedReportMatchFailures; - if (reportMatchFailures && result0 === null) { + reportFailures--; + if (reportFailures === 0 && result0 === null) { matchFailed("whitespace"); } - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } function parse_whitespace() { - var cacheKey = 'whitespace@' + pos; - var cachedResult = cache[cacheKey]; - if (cachedResult) { - pos = cachedResult.nextPos; - return cachedResult.result; - } - + var result0; - if (input.substr(pos).match(/^[ \n\r]/) !== null) { - var result0 = input.charAt(pos); + if (/^[ \t\n\r]/.test(input.charAt(pos))) { + result0 = input.charAt(pos); pos++; } else { - var result0 = null; - if (reportMatchFailures) { - matchFailed("[ \\n\\r]"); + result0 = null; + if (reportFailures === 0) { + matchFailed("[ \\t\\n\\r]"); } } - - - - cache[cacheKey] = { - nextPos: pos, - result: result0 - }; return result0; } - function buildErrorMessage() { - function buildExpected(failuresExpected) { - failuresExpected.sort(); - - var lastFailure = null; - var failuresExpectedUnique = []; - for (var i = 0; i < failuresExpected.length; i++) { - if (failuresExpected[i] !== lastFailure) { - failuresExpectedUnique.push(failuresExpected[i]); - lastFailure = failuresExpected[i]; - } - } - - switch (failuresExpectedUnique.length) { - case 0: - return 'end of input'; - case 1: - return failuresExpectedUnique[0]; - default: - return failuresExpectedUnique.slice(0, failuresExpectedUnique.length - 1).join(', ') - + ' or ' - + failuresExpectedUnique[failuresExpectedUnique.length - 1]; + + function cleanupExpected(expected) { + expected.sort(); + + var lastExpected = null; + var cleanExpected = []; + for (var i = 0; i < expected.length; i++) { + if (expected[i] !== lastExpected) { + cleanExpected.push(expected[i]); + lastExpected = expected[i]; } } - - var expected = buildExpected(rightmostMatchFailuresExpected); - var actualPos = Math.max(pos, rightmostMatchFailuresPos); - var actual = actualPos < input.length - ? quote(input.charAt(actualPos)) - : 'end of input'; - - return 'Expected ' + expected + ' but ' + actual + ' found.'; + return cleanExpected; } function computeErrorPosition() { @@ -3858,13 +3076,13 @@ module.exports = (function(){ var column = 1; var seenCR = false; - for (var i = 0; i < rightmostMatchFailuresPos; i++) { + for (var i = 0; i < Math.max(pos, rightmostFailuresPos); i++) { var ch = input.charAt(i); - if (ch === '\n') { + if (ch === "\n") { if (!seenCR) { line++; } column = 1; seenCR = false; - } else if (ch === '\r' | ch === '\u2028' || ch === '\u2029') { + } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") { line++; column = 1; seenCR = true; @@ -3878,208 +3096,107 @@ module.exports = (function(){ } - - var filterEqual = function(o, k, v) { o[k] = v; }, - - filterGreater = filter("$gt"), - - filterGreaterOrEqual = filter("$gte"), - - filterLess = filter("$lt"), - - filterLessOrEqual = filter("$lte"), - - filterNotEqual = filter("$ne"), - - filterRegularExpression = filter("$regex"), - - filterIn = filter("$in"), - - exists = {$exists: true}; - - - - function add(a, b) { return a + b; } - - function subtract(a, b) { return a - b; } - - function multiply(a, b) { return a * b; } - - function divide(a, b) { return a / b; } - - - - function one() { return 1; } - - function noop() {} - - - - function filter(op) { - - return function(o, k, v) { - - var f = o[k]; - - switch (typeof f) { - - case "undefined": o[k] = f = {}; // continue - - case "object": f[op] = v; break; - - // otherwise, observe the existing equals (literal) filter - + var filterEqual = function(o, k, v) { o[k] = v; }, + filterGreater = filter("$gt"), + filterGreaterOrEqual = filter("$gte"), + filterLess = filter("$lt"), + filterLessOrEqual = filter("$lte"), + filterNotEqual = filter("$ne"), + filterRegularExpression = filter("$regex"), + filterIn = filter("$in"), + exists = {$exists: true}; + + function add(a, b) { return a + b; } + function subtract(a, b) { return a - b; } + function multiply(a, b) { return a * b; } + function divide(a, b) { return a / b; } + + function one() { return 1; } + function noop() {} + + function filter(op) { + return function(o, k, v) { + var f = o[k]; + switch (typeof f) { + case "undefined": o[k] = f = {}; // continue + case "object": f[op] = v; break; + // otherwise, observe the existing equals (literal) filter + } + }; } - }; - - } - - - - function arrayAccessor(name) { - - name = new String(name); - - name.array = true; - - return name; - - } - - - - function objectAccessor(name) { - - return name; - - } - - - - function compoundMetric(head, tail) { - - var i = -1, - - n = tail.length, - - t, - - e = head; - - while (++i < n) { - - t = tail[i]; - - e = {left: e, op: t[1], right: t[3]}; - - if (!i) head = e; - - } - - return head; - - } - - - - function compoundValue(head, tail) { - - var n = tail.length; - - return { - - exists: function(o) { - - var i = -1; - - head.exists(o); - - while (++i < n) tail[i][3].exists(o); - - }, - - fields: function(o) { - - var i = -1; - - head.fields(o); - - while (++i < n) tail[i][3].fields(o); - - }, - - value: function(o) { - - var v = head.value(o), - - i = -1, - - t; - - while (++i < n) v = (t = tail[i])[1](v, t[3].value(o)); - - return v; - + function arrayAccessor(name) { + name = new String(name); + name.array = true; + return name; } - }; - - } - - - - function member(head, tail) { - - var fields = ["d", head].concat(tail), - - shortName = fields.filter(function(d) { return !d.array; }).join("."), - - longName = fields.join("."), - - i = -1, - - n = fields.length; - - return { - - field: longName, - - exists: function(o) { - - if (!(shortName in o)) { - - o[shortName] = exists; - - } - - }, - - fields: function(o) { - - o[shortName] = 1; - - }, - - value: function(o) { - - var i = -1; + function objectAccessor(name) { + return name; + } + function compoundMetric(head, tail) { + var i = -1, + n = tail.length, + t, + e = head; while (++i < n) { - - o = o[fields[i]]; - - } - - return o; - + t = tail[i]; + e = {left: e, op: t[1], right: t[3]}; + if (!i) head = e; + } + return head; + } + + function compoundValue(head, tail) { + var n = tail.length; + return { + exists: function(o) { + var i = -1; + head.exists(o); + while (++i < n) tail[i][3].exists(o); + }, + fields: function(o) { + var i = -1; + head.fields(o); + while (++i < n) tail[i][3].fields(o); + }, + value: function(o) { + var v = head.value(o), + i = -1, + t; + while (++i < n) v = (t = tail[i])[1](v, t[3].value(o)); + return v; + } + }; } - }; - - } + function member(head, tail) { + var fields = ["d", head].concat(tail), + shortName = fields.filter(function(d) { return !d.array; }).join("."), + longName = fields.join("."), + i = -1, + n = fields.length; + return { + field: longName, + exists: function(o) { + if (!(shortName in o)) { + o[shortName] = exists; + } + }, + fields: function(o) { + o[shortName] = 1; + }, + value: function(o) { + var i = -1; + while (++i < n) { + o = o[fields[i]]; + } + return o; + } + }; + } - var result = parseFunctions[startRule](); @@ -4090,27 +3207,32 @@ module.exports = (function(){ * * - |result !== null| * - |pos === input.length| - * - |rightmostMatchFailuresExpected| may or may not contain something + * - |rightmostFailuresExpected| may or may not contain something * * 2. The parser successfully parsed only a part of the input. * * - |result !== null| * - |pos < input.length| - * - |rightmostMatchFailuresExpected| may or may not contain something + * - |rightmostFailuresExpected| may or may not contain something * * 3. The parser did not successfully parse any part of the input. * * - |result === null| * - |pos === 0| - * - |rightmostMatchFailuresExpected| contains at least one failure + * - |rightmostFailuresExpected| contains at least one failure * * All code following this comment (including called functions) must * handle these states. */ if (result === null || pos !== input.length) { + var offset = Math.max(pos, rightmostFailuresPos); + var found = offset < input.length ? input.charAt(offset) : null; var errorPosition = computeErrorPosition(); + throw new this.SyntaxError( - buildErrorMessage(), + cleanupExpected(rightmostFailuresExpected), + found, + offset, errorPosition.line, errorPosition.column ); @@ -4125,9 +3247,33 @@ module.exports = (function(){ /* Thrown when a parser encounters a syntax error. */ - result.SyntaxError = function(message, line, column) { - this.name = 'SyntaxError'; - this.message = message; + result.SyntaxError = function(expected, found, offset, line, column) { + function buildMessage(expected, found) { + var expectedHumanized, foundHumanized; + + switch (expected.length) { + case 0: + expectedHumanized = "end of input"; + break; + case 1: + expectedHumanized = expected[0]; + break; + default: + expectedHumanized = expected.slice(0, expected.length - 1).join(", ") + + " or " + + expected[expected.length - 1]; + } + + foundHumanized = found ? quote(found) : "end of input"; + + return "Expected " + expectedHumanized + " but " + foundHumanized + " found."; + } + + this.name = "SyntaxError"; + this.expected = expected; + this.found = found; + this.message = buildMessage(expected, found); + this.offset = offset; this.line = line; this.column = column; }; diff --git a/lib/cube/metric-expression.peg b/lib/cube/metric-expression.peg index 80aa9bcf..666626bb 100644 --- a/lib/cube/metric-expression.peg +++ b/lib/cube/metric-expression.peg @@ -117,10 +117,13 @@ metric_unary_expression / metric_primary_expression metric_primary_expression - = reduce:reduce _ "(" _ event:event_expression _ ")" { event.reduce = reduce; event.source = input.substring(savedPos3, pos); return event; } + = reduce:reduce _ "(" _ event:event_expression _ ")" group:(_ "." _ metric_group_expression)? { event.reduce = reduce; event.source = input.substring(pos0, pos); if(group) event.group = group[3]; return event; } / value:number { return {value: function() { return value; }}; } / "(" _ expression:metric_additive_expression _ ")" { return expression; } +metric_group_expression + = "group" _ "(" _ member:event_member_expression _ ")" { return member; } + event_expression = value:event_value_expression filters:(_ "." _ event_filter_expression)* { diff --git a/lib/cube/metric.js b/lib/cube/metric.js index 7f997803..5abd2e2c 100644 --- a/lib/cube/metric.js +++ b/lib/cube/metric.js @@ -1,247 +1,54 @@ +'use strict'; + // TODO use expression ids or hashes for more compact storage -var parser = require("./metric-expression"), - tiers = require("./tiers"), - types = require("./types"), +var _ = require("underscore"), + util = require("util"), + queuer = require("queue-async"), + parser = require("./metric-expression"), + tiers = require("./tiers"), reduces = require("./reduces"), - event = require("./event"), - setImmediate = require("./set-immediate"); - -var metric_fields = {v: 1}, - metric_options = {sort: {"_id.t": 1}, batchSize: 1000}, - event_options = {sort: {t: 1}, batchSize: 1000}; + Metric = require("./models/metric"), + Measurement = require("./models/measurement"), + event = require("./event"), + metalog = require('./metalog'); // Query for metrics. + exports.getter = function(db) { - var collection = types(db), - Double = db.bson_serializer.Double, - queueByName = {}, - meta = event.putter(db); + var streamsBySource = {}; function getter(request, callback) { - var start = new Date(request.start), - stop = new Date(request.stop), - id = request.id; + var measurement, expression, + tier = tiers[+request.step], + start = new Date(request.start), + stop = new Date(request.stop); - // Validate the dates. - if (isNaN(start)) return callback({error: "invalid start"}), -1; - if (isNaN(stop)) return callback({error: "invalid stop"}), -1; - - // Parse the expression. - var expression; try { - expression = parser.parse(request.expression); - } catch (e) { - return callback({error: "invalid expression"}), -1; - } - - // Round start and stop to the appropriate time step. - var tier = tiers[+request.step]; - if (!tier) return callback({error: "invalid step"}), -1; - start = tier.floor(start); - stop = tier.ceil(stop); - - // Compute the request metric! - measure(expression, start, stop, tier, "id" in request - ? function(time, value) { callback({time: time, value: value, id: id}); } - : function(time, value) { callback({time: time, value: value}); }); - } - - // Computes the metric for the given expression for the time interval from - // start (inclusive) to stop (exclusive). The time granularity is determined - // by the specified tier, such as daily or hourly. The callback is invoked - // repeatedly for each metric value, being passed two arguments: the time and - // the value. The values may be out of order due to partial cache hits. - function measure(expression, start, stop, tier, callback) { - (expression.op ? binary : expression.type ? unary : constant)(expression, start, stop, tier, callback); - } - - // Computes a constant expression; - function constant(expression, start, stop, tier, callback) { - var value = expression.value(); - while (start < stop) { - callback(start, value); - start = tier.step(start); - } - callback(stop); - } - - // Serializes a unary expression for computation. - function unary(expression, start, stop, tier, callback) { - var remaining = 0, - time0 = Date.now(), - time = start, - name = expression.source, - queue = queueByName[name], - step = tier.key; - - // Compute the expected number of values. - while (time < stop) ++remaining, time = tier.step(time); - - // If no results were requested, return immediately. - if (!remaining) return callback(stop); - - // Add this task to the appropriate queue. - if (queue) queue.next = task; - else setImmediate(task); - queueByName[name] = task; - - function task() { - findOrComputeUnary(expression, start, stop, tier, function(time, value) { - callback(time, value); - if (!--remaining) { - callback(stop); - if (task.next) setImmediate(task.next); - else delete queueByName[name]; - - // Record how long it took us to compute as an event! - var time1 = Date.now(); - meta({ - type: "cube_compute", - time: time1, - data: { - expression: expression.source, - ms: time1 - time0 - } - }); - } - }); + if (!tier) throw "invalid step"; + if (isNaN(start)) throw "invalid start"; + if (isNaN(stop)) throw "invalid stop"; + + // Round start and stop to the appropriate time step. + start = tier.floor(start); + stop = tier.ceil(stop); + expression = parser.parse(request.expression); + measurement = new Measurement(expression, start, stop, tier); + + measurement.on('complete', function(){ callback(new Metric({time: stop, value: null}, measurement)); }); + } catch(error) { + metalog.error('mget', error, { info: util.inspect([start, stop, tier, expression] )}); + return callback({error: error, _trace: request._trace}), -1; } - } - - // Finds or computes a unary (primary) expression. - function findOrComputeUnary(expression, start, stop, tier, callback) { - var name = expression.type, - type = collection(name), - map = expression.value, - reduce = reduces[expression.reduce], - filter = {t: {}}, - fields = {t: 1}; - - // Copy any expression filters into the query object. - expression.filter(filter); - - // Request any needed fields. - expression.fields(fields); - - find(start, stop, tier, callback); - - // The metric is computed recursively, reusing the above variables. - function find(start, stop, tier, callback) { - var compute = tier.next && reduce.pyramidal ? computePyramidal : computeFlat, - step = tier.key; - - // Query for the desired metric in the cache. - type.metrics.find({ - i: false, - "_id.e": expression.source, - "_id.l": tier.key, - "_id.t": { - $gte: start, - $lt: stop - } - }, metric_fields, metric_options, foundMetrics); - - // Immediately report back whatever we have. If any values are missing, - // merge them into contiguous intervals and asynchronously compute them. - function foundMetrics(error, cursor) { - handle(error); - var time = start; - cursor.each(function(error, row) { - handle(error); - if (row) { - callback(row._id.t, row.v); - if (time < row._id.t) compute(time, row._id.t); - time = tier.step(row._id.t); - } else { - if (time < stop) compute(time, stop); - } - }); - } - - // Group metrics from the next tier. - function computePyramidal(start, stop) { - var bins = {}; - find(start, stop, tier.next, function(time, value) { - var bin = bins[time = tier.floor(time)] || (bins[time] = {size: tier.size(time), values: []}); - if (bin.values.push(value) === bin.size) { - save(time, reduce(bin.values)); - delete bins[time]; - } - }); - } - - // Group raw events. Unlike the pyramidal computation, here we can control - // the order in which rows are returned from the database. Thus, we know - // when we've seen all of the events for a given time interval. - function computeFlat(start, stop) { - filter.t.$gte = start; - filter.t.$lt = stop; - type.events.find(filter, fields, event_options, function(error, cursor) { - handle(error); - var time = start, values = []; - cursor.each(function(error, row) { - handle(error); - if (row) { - var then = tier.floor(row.t); - if (time < then) { - save(time, values.length ? reduce(values) : reduce.empty); - while ((time = tier.step(time)) < then) save(time, reduce.empty); - values = [map(row)]; - } else { - values.push(map(row)); - } - } else { - save(time, values.length ? reduce(values) : reduce.empty); - while ((time = tier.step(time)) < stop) save(time, reduce.empty); - } - }); - }); - } - - function save(time, value) { - callback(time, value); - if (value) { - type.metrics.save({ - _id: { - e: expression.source, - l: tier.key, - t: time - }, - i: false, - v: new Double(value) - }, handle); - } - } - } - } - - // Computes a binary expression by merging two subexpressions. - function binary(expression, start, stop, tier, callback) { - var left = {}, right = {}; - - measure(expression.left, start, stop, tier, function(t, l) { - if (t in right) { - callback(t, t < stop ? expression.op(l, right[t]) : l); - delete right[t]; - } else { - left[t] = l; - } - }); - measure(expression.right, start, stop, tier, function(t, r) { - if (t in left) { - callback(t, t < stop ? expression.op(left[t], r) : r); - delete left[t]; - } else { - right[t] = r; - } - }); + measurement.measure(db, callback); } return getter; }; function handle(error) { - if (error) throw error; + if (!error) return; + metalog.error('metric', error); + throw error; } diff --git a/lib/cube/models/event.js b/lib/cube/models/event.js new file mode 100644 index 00000000..cfdf2c19 --- /dev/null +++ b/lib/cube/models/event.js @@ -0,0 +1,156 @@ +'use strict'; + +var _ = require("underscore"), + metalog = require('../metalog'); + +var tiers = require("../tiers"), + Model = require("../core_ext/model"), + tensec = tiers[tiers.units['second10']], + type_re = /^[a-z][a-zA-Z0-9_]+$/, + event_options = {sort: {t: 1}, batchSize: 1000}; + +_.mapHash = function(obj, func){ + var res = {}; + _.each(obj, function(val, key){ res[key] = func(val, key, res); }); + return res; +}; + +function formatData(data){ + if (!_.isObject(data)) return data; + if (Array.isArray(data)) return data.map(formatData); + _.keys(data).forEach(function(key){ + data[key] = formatData(data[key]); + + if (_.isNumber(key) || /^[0-9]$/.test(key)) { + var val = data[key]; delete data[key]; + data['k' + key] = val; + } + }); + return data; +} + +function Event(type, time, data, id){ + this.time = time; + this.data = formatData(data); + if (id) this.id = id; + + this.setProperty("type", { value: type }); +} + +Model.modelize(Event); + +Event.setProperties({ + bin: { value: function(tr){ return tiers[tr].bin(this.time); }}, + day_bin: { value: function(){ return tiers[day ].bin(this.time); }}, + m05_bin: { value: function(){ return tiers[minute5 ].bin(this.time); }}, + s10_bin: { value: function(){ return tiers[second10].bin(this.time); }}, + day_ago: { value: function day_ago(){ return Math.floor((Date.now() - this.time) / day); }}, + m05_ago: { value: function m05_ago(){ return Math.floor((Date.now() - this.time) / minute5); }}, + s10_ago: { value: function s10_ago(){ return Math.floor((Date.now() - this.time) / second10); }}, + bins: { value: function bins(){ return tiers.bins(this.time); }}, + agos: { value: function agos(){ return [this.day_ago(), this.m05_ago(), this.s10_ago() ]; }}, + report: { value: function report(){ + return { time: this.time, type: this.type, bin: this.bins(), ago: this.agos() }; + }}, + + to_wire: { value: function(){ var event = { t: this.time, d: this.data }; if (this.id) event._id = this.id; return event; }}, + + save: { value: save }, + validate: { value: validate }, + to_request: {value: to_request } +}); + +function find(db, measurement, callback){ + var expression = measurement.expression, + group = expression.group, + type = expression.type, + start = measurement.start, + stop = measurement.stop, + filter = {t: {}}, + fields = {t: 1}; + + // Copy any expression filters into the query object. + expression.filter(filter); + filter.t.$gte = start; + filter.t.$lt = stop; + + if(group) { + filter[group.field] = measurement.group; + fields[group.field] = 1; + } + + // Request any needed fields. + expression.fields(fields); + + db.events(type, function (error, collection) { + if(error) return callback(error); + + function handleResponse(error, cursor){ + var group_name; + if (error) return callback(error); + cursor.each(function(error, row) { + if (error) return callback(error); + if (row) callback(error, new Event(type, row.t, row.d, row._id)); + else { + callback(); + } + }); + } + + collection.find(filter, fields, event_options, handleResponse); + }); +} +Object.defineProperty(Event, "find", { value: find }); + +function groups(db, measurement, callback){ + var group = measurement.isGrouped, + type = group.type, + start = measurement.start, + stop = measurement.stop, + filter = {t: { $gte: start, $lt: stop }}; + + // Copy any expression filters into the query object. + group.filter(filter); + + db.events(type, function (error, collection) { + if(error) return callback(error); + collection.distinct(group.field, filter, callback); + }); +} +Object.defineProperty(Event, "groups", { value: groups }); + +function save(db, callback){ + var self = this; + if (this.validate) { + try{ this.validate(); } + catch(error) { return callback(error); } + } + + db.events(self.type, function event_saver(error, collection){ + if (error) return callback(error); + collection.save(self.to_wire(), function saver(error){ + callback(error, self); + }); + }); +}; + +// Validate the date and type. +function validate(){ + if (!type_re.test(this.type)) throw("invalid type"); + if (isNaN(this.time)) throw("invalid time"); +}; + +function to_request(attrs){ + var ev = { time: this.time, data: this.data, type: this.type }; + if (this.id) ev.id = this.id; + for (var key in attrs){ ev[key] = attrs[key]; } + return ev; +}; + +function handle(error) { + if (!error) return; + metalog.error('event', error); + throw error; +} + +module.exports = Event; diff --git a/lib/cube/models/invalidator.js b/lib/cube/models/invalidator.js new file mode 100644 index 00000000..dd9c4621 --- /dev/null +++ b/lib/cube/models/invalidator.js @@ -0,0 +1,68 @@ +'use strict'; + +var _ = require("underscore"), + metalog = require('../metalog'), + tiers = require("../tiers"), + Model = require("../core_ext/model"); + + +// How frequently to invalidate metrics after receiving events. +var invalidateInterval = 5000; + +// Schedule deferred invalidation of metrics by type and tier. +function Invalidator(){ + this.type_tsets = {}, + this.invalidate = { $set: {i: true} }, + this.invalidationOptions = { multi: true, w: 0 }; +} + +Model.modelize(Invalidator); + +function add(type, ev){ + var tt = this.type_tset(type); + for (var tier in tiers){ tt[tier][tier*Math.floor(ev.time/tier)] = true; } +}; + +function flush(db, callback){ + var _this = this; + _.each(_this.type_tsets, function(type_tset, type){ + db.metrics(type, function(error, collection){ + callback(error); + + _.each(type_tset, function(tset, tier){ + var times = dateify(tset); + metalog.info("event_flush", { type: type, tier: tier, times: times }); + collection.update({ i: false, "_id.l": +tier, "_id.t": {$in: times}}, _this.invalidate, _this.invalidationOptions); + }); + }); + }); +}; + +function tsets(){ return _.mapHash(this.type_tsets, function(tt, type){ return _.mapHash(tt, dateify); }); }; + +Invalidator.setProperties({ + add: { value: add }, + flush: { value: flush }, + tsets: { get: tsets }, + type_tset: { value: type_tset } +}); + +function type_tset(type){ + if (! (type in this.type_tsets)) this.type_tsets[type] = _.mapHash(tiers, function(){ return {}; });; + return this.type_tsets[type]; +}; +function dateify(tset){ + return _.map(_.keys(tset), function(time){ + return new Date(+time); + }).sort(function(aa,bb){return aa-bb;}); +} + +Invalidator.flushers = {}; +Invalidator.start_flusher = function(id, cb){ Invalidator.flushers[id] = setInterval(cb, invalidateInterval); }; +Invalidator.stop_flusher = function(id, on_stop){ + clearInterval(Invalidator.flushers[id]); + delete Invalidator.flushers[id]; + if (on_stop) on_stop(); +}; + +module.exports = Invalidator; diff --git a/lib/cube/models/measurement.js b/lib/cube/models/measurement.js new file mode 100644 index 00000000..2f1b51d8 --- /dev/null +++ b/lib/cube/models/measurement.js @@ -0,0 +1,360 @@ +'use strict'; + +var metalog = require('../metalog'), + Model = require('../core_ext/model'), + reduces = require('../reduces'), + Metric = require('./metric'), + Event = require('./event'), + _ = require('underscore'), + config = require('../config'), + compute = {constant: constant, binary: binary, unary: unary}, + setImmediate = require("../set-immediate"), + queueByName = {}; + +function isGrouped(expression){ + if (expression.type && expression.group){ + expression.group.type = expression.type; + expression.group.filter = expression.filter; + return expression.group; + } + if (expression.op) return (isGrouped(expression.left) || isGrouped(expression.right)); + return; +} + +function Measurement(expression, start, stop, tier){ + // Round the start/stop to the tier edges + this.expression = expression; + this.start = start; + this.stop = stop; + this.tier = tier; + this.flavor = (expression.op ? 'binary' : (expression.type ? 'unary' : 'constant')); + this.isPyramidal = expression.type && reduces[expression.reduce].pyramidal; + this.isGrouped = isGrouped(expression); + + this.eventize(); +} + +Model.modelize(Measurement); + +Measurement.prototype.report = function report(){ + return { flavor: this.flavor, tier: this.tier.key, start: this.tier.bin(this.start), stop: this.tier.bin(this.stop), expr: (this.expression.op||this.expression.source||this.expression.value()) }; +}; + +// Computes the metric for the given expression for the time interval from +// start (inclusive) to stop (exclusive). The time granularity is determined +// by the specified tier, such as daily or hourly. The callback is invoked +// repeatedly for each metric value. The values may be out of order due +// to partial cache hits. +Measurement.prototype.measure = function measure(db, callback) { + var _this = this; + if (this.isGrouped && !Array.isArray(this.isGrouped.groups)) { + Event.groups(db, this, function(error, group_names){ + handle(error); + _this.isGrouped.groups = group_names.sort(); + compute[_this.flavor].call(_this, db, callback); + }); + } else { + compute[this.flavor].call(this, db, callback); + } +}; + +// Computes a constant expression like the "7" in "x * 7" +function constant(db, callback) { + var _this = this, value = this.expression.value(); + walk(this.start, this.stop, this.tier, function(time){ + callback(new Metric({time: time, value: value}, _this)); + }); + this.emit('complete'); +}; + +// Serializes a unary expression for computation. +function unary(db, callback) { + var self = this, + total_remaining = 0, + remaining = 0, + time0 = Date.now(), + name = this.expression.source, + queue = queueByName[name]; + + // Compute the expected number of values. + walk(this.start, this.stop, this.tier, function(time){ ++remaining; }); + + // If no results were requested, return immediately. + if (!remaining) return this.emit('complete'); + + total_remaining = remaining; + if(this.isGrouped) total_remaining *= (this.isGrouped.groups.length || 1); + + // Add this task to the appropriate queue. + if (queue) queue.next = task; + else setImmediate(task); + queueByName[name] = task; + + function task() { + function onMetric(metric){ + callback(metric); + if (--total_remaining <= 0) { + self.emit('complete'); + if (task.next) setImmediate(task.next); + else delete queueByName[name]; + + // Record how long it took us to compute as an event! + var time1 = Date.now(); + metalog.event("cube_compute", { + expression: self.expression.source, + ms: time1 - time0 + }); + } + } + + function nextGroup(prev_group){ + var query_measurement = new Measurement(_.clone(self.expression), self.start, self.stop, self.tier), + group_idx = self.isGrouped.groups.indexOf(prev_group) + 1, + group_name = self.isGrouped.groups[group_idx], + group_remaining = remaining; + + // Stop there if it is the last group, unless we are in the 1st pass, + // which would mean we have no group at all, so let's compute empty values. + if (group_idx >= self.isGrouped.groups.length && arguments.length) return; + + query_measurement.group = group_name; + + findOrComputeUnary.call(query_measurement, db, function(metric){ + onMetric(metric); + if(!--group_remaining) nextGroup(group_name); + }); + } + + if (self.expression.group) { + nextGroup(); + } else { + findOrComputeUnary.call(self, db, onMetric); + } + } +} + +// Finds or computes a unary (primary) expression. +function findOrComputeUnary(db, callback) { + var expression = this.expression, + group_name = this.group, + isGrouped = 'group' in this, + map = expression.value, + reduce = reduces[expression.reduce], + measurement = this; + + find(measurement, callback); + + // The metric is computed recursively, reusing the above variables. + function find(measurement, callback) { + var start = measurement.start, + stop = measurement.stop, + tier = measurement.tier, + compute = ((tier.next) ? computePyramidal : computeFlat), + time = start; + + // Query for the desired metric in the cache. + Metric.find(db, measurement, foundMetrics); + + // Immediately report back whatever we have. If any values are missing, + // merge them into contiguous intervals and asynchronously compute them. + function foundMetrics(error, metric) { + handle(error); + if (metric) { + callback(metric); // send back value for this timeslot + if (time < metric.time) compute(time, metric.time); // recurse from last value seen up to this timeslot + time = tier.step(metric.time); // update the last-observed timeslot + } else { + if (time < stop) compute(time, stop); // once last row is seen, compute rest of range + } + } + + // Group metrics from the next tier. + function computePyramidal(start, stop) { + var query_measurement = new Measurement(expression, start, stop, tier.next), + bins = {}; + + if(isGrouped) query_measurement.group = group_name; + + find(query_measurement, function(metric) { + var value = metric.value, time = metric.time, values = metric.values; + + var bin = bins[time = tier.floor(time)] || (bins[time] = {size: tier.size(time), values: []}); + + if (reduce.pyramidal) bin.values.push(value); + else bin.values = bin.values.concat(values||[]); + + if (!--bin.size) { + var metric; + if (isGrouped) { + metric = new Metric({time: time, value: reduce(bin.values), group: group_name}, measurement, bin.values) + } else { + metric = new Metric({time: time, value: reduce(bin.values)}, measurement, bin.values) + } + + if (metric.value || metric.value === 0 || metric.value === undefined) metric.save(db, handle); + callback(metric); + delete bins[time]; + } + }); + } + + // Group raw events. Unlike the pyramidal computation, here we can control + // the order in which rows are returned from the database. Thus, we know + // when we've seen all of the events for a given time interval. + function computeFlat(start, stop) { + var horizons = config.horizons, pastHorizon = { + is: 'past_horizon', + tier: tier, + expression: expression.source + }; + + // Reset start time to calculation horizon if requested time span goes past it + if (horizons && tier.floor(start) < new Date(new Date() - horizons.calculation)){ + var old_start = start, + start = tier.step(tier.floor(new Date(new Date() - horizons.calculation))) + + pastHorizon.start = { was: old_start, updated_to: start }; + } + + // Reset stop time to calculation horizon if requested time span goes past it + if (horizons && tier.floor(stop) < new Date(new Date() - horizons.calculation)){ + var old_stop = stop, + stop = tier.step(tier.floor(new Date(new Date() - horizons.calculation))) + + pastHorizon.stop = { was: old_stop, updated_to: stop }; + } + + if (old_start || old_stop) { + metalog.info('cube_compute', pastHorizon); + } + + var time = start, values = []; + + if (isGrouped && group_name === undefined) { + // We do have grouping enabled, but no group at all, so answer right away with empty metrics. + return process(); + } + + var query_measurement = new Measurement(expression, start, stop, tier); + + if (group_name) query_measurement.group = group_name; + + function flat_callback(time, values){ + var value = (values.length ? reduce(values) : reduce.empty), + metric; + if (isGrouped) { + metric = new Metric({ time: time, value: value, group: group_name }, measurement, values); + } else { + metric = new Metric({ time: time, value: value }, measurement, values); + } + callback(metric); + if (metric.value || metric.value === 0 || metric.value === undefined) metric.save(db, handle); + } + + function process(error, event){ + handle(error); + + if (event) { + var then = tier.floor(event.time); + + if (time < then) { + flat_callback(time, values); + while ((time = tier.step(time)) < then) flat_callback(time, []); + values = [map(event.to_wire())]; + } else { + values.push(map(event.to_wire())); + } + } else { + flat_callback(time, values); + while ((time = tier.step(time)) < stop) flat_callback(time, []); + } + } + + Event.find(db, query_measurement, process); + } + } +} + +// Computes a binary expression by merging two subexpressions +// +// "sum(req) - sum(resp)" will op ('-') the result of unary "sum(req)" and +// unary "sum(resp)". We don't know what order they'll show up in, so if say +// the value for left appears first, it parks that value as left[time], where +// the result for right will eventually find it. +function binary(db, callback) { + var self = this, expression = this.expression, value; + var left = new Measurement(expression.left, this.start, this.stop, this.tier), + right = new Measurement(expression.right, this.start, this.stop, this.tier), + groups, grouped_measurement = left, other_measurement = right; + + left.left = true; + right.right = true; + + function complete(measurement, other){ + return function(){ + var time = self.stop; + if (time in other){ + self.emit("complete"); + } else { + measurement[time] = undefined; + } + } + } + + function measure(measurement, other){ + return function(metric){ + var time = metric.time, value = metric.value, isGrouped = 'group' in metric, + group = metric.group, left_value, right_value; + + if (time in other) { + left_value = measurement.left ? value : other[time]; + right_value = measurement.right ? value : other[time]; + if (isGrouped) { + metric = { time: time, value: expression.op(left_value, right_value), group: group }; + } else { + metric = { time: time, value: expression.op(left_value, right_value) }; + } + callback(new Metric(metric)); + } else { + measurement[time] = value; + } + } + } + + if(this.isGrouped){ + groups = this.isGrouped.groups; + if(right.isGrouped){ + grouped_measurement = right; + other_measurement = left; + } + + other_measurement.on("complete", function(){ + complete(other_measurement, grouped_measurement)(); + grouped_measurement.measure(db, measure(grouped_measurement, other_measurement)); + }); + grouped_measurement.on("complete", complete(grouped_measurement, other_measurement)); + other_measurement.measure(db, measure(other_measurement, grouped_measurement)); + } else { + left.on("complete", complete(left, right)); + right.on("complete", complete(right, left)); + + left.measure(db, measure(left, right)); + right.measure(db, measure(right, left)); + } +}; + +// execute cb on each interval from t1 to t2 +function walk(t1, t2, tier, cb){ + while (t1 < t2) { + cb(t1, t2); + t1 = tier.step(t1); + } +} + +function handle(error) { + if (!error) return; + metalog.error('measurement', error); + throw error; +} + +module.exports = Measurement; diff --git a/lib/cube/models/metric.js b/lib/cube/models/metric.js new file mode 100644 index 00000000..ef253b2f --- /dev/null +++ b/lib/cube/models/metric.js @@ -0,0 +1,134 @@ +'use strict'; + +var _ = require("underscore"), + metalog = require('../metalog'), + mongo = require('mongodb'), + Model = require("../core_ext/model"); + +var second = 1e3, + second10 = 10e3, + minute = 60e3, + minute5 = 300e3, + hour = 3600e3, + day = 86400e3; + +var tiers = require("../tiers"), + tensec = tiers[second10], + type_re = /^[a-z][a-zA-Z0-9_]+$/, + metric_fields = {v: 1, vs: 1}, + metric_options = {sort: {"_id.t": 1}, batchSize: 1000}; + +function Metric(data, measurement, values){ + this.time = data.time; + this.value = data.value; + if('group' in data) this.group = data.group === undefined ? null : data.group; + + this.setProperty("values", { value: values||[] }); + this.setProperty("measurement", { value: measurement }); +} + +Model.modelize(Metric); + +Metric.setProperties({ + tier: { get: function(){ return this.measurement.tier } }, + bin: { get: function(){ return this.tier.bin(this.time); } }, + e: { get: function(){ return this.measurement.expression.source }}, + l: { get: function(){ return this.measurement.tier.key }}, + type: { get: function(){ return this.measurement.expression.type }}, + + to_wire: { value: to_wire }, + report: { value: report }, + save: { value: save } +}); + +function find(db, measurement, callback){ + var expression = measurement.expression, + start = measurement.start, + stop = measurement.stop, + type = expression.type, + tier = measurement.tier; + + db.metrics(type, function(error, collection){ + if (error) return callback(error); + + var query = { + i: false, + "_id.e": expression.source, + "_id.l": tier.key, + "_id.t": { + $gte: start, + $lt: stop + } + }; + if ('group' in measurement) query["_id.g"] = measurement.group; + + collection.find(query, metric_fields, metric_options, handleResponse); + }); + + function handleResponse(error, cursor){ + if (error) return callback(error); + cursor.each(function(error, row) { + if (error) return callback(error); + if (row) callback(error, Metric.from_wire(row, measurement)); + else callback(); + }) + } +} +Object.defineProperty(Metric, "find", { value: find }); +Object.defineProperty(Metric, "from_wire", { value: from_wire }); + +function from_wire(row, measurement){ + var values = null, + data; + if(!measurement.isPyramidal) { + values = (row.vs || []).reduce(function(expanded, value){ + _.times(value.c, function(){ expanded.push(value.v); }); + return expanded; + }, []); + } + + data = {time: row._id.t, value: row.v}; + if('g' in row._id) data.group = row._id.g; + + return new Metric(data, measurement, values); +} + +function to_wire(){ + var values = null, + id; + if(!this.measurement.isPyramidal){ + values = this.values.reduce(function(values, value){ + var pair = _.find(values, function(pair){ return pair.v == value; }); + if (!pair) values.push(pair = { v: value, c: 0 }); + pair.c++; + return values; + }, []); + } + id = { e: this.e, l: this.l, t: this.time }; + if('group' in this) id.g = this.group; + + if (this.value === undefined) { + return { i: false, vs: values, _id: id}; + } else { + return { i: false, v: mongo.Double(this.value), vs: values, _id: id}; + } +}; + +function report(){ + var hsh = { time: this.time, value: this.value }; + return hsh; +}; + +function save(db, callback){ + var self = this; + if (this.validate) this.validate(); + + db.metrics(self.type, function(error, collection){ + if (error) return callback(error); + collection.save(self.to_wire(), function(error){ + callback(error, self); + }); + }); +}; + +module.exports = Metric; diff --git a/lib/cube/reduces.js b/lib/cube/reduces.js index 58b25176..d73faac7 100644 --- a/lib/cube/reduces.js +++ b/lib/cube/reduces.js @@ -1,3 +1,5 @@ +'use strict'; + var reduces = module.exports = { sum: function(values) { @@ -8,24 +10,24 @@ var reduces = module.exports = { min: function(values) { var i = -1, n = values.length, min = Infinity, value; - while (++i < n) if ((value = values[i]) < min) min = value; - return min; + while (++i < n){ if ((value = values[i]) < min){ min = value; } } + return isFinite(min) ? min : undefined; }, max: function(values) { var i = -1, n = values.length, max = -Infinity, value; - while (++i < n) if ((value = values[i]) > max) max = value; - return max; + while (++i < n){ if ((value = values[i]) > max){ max = value; } } + return isFinite(max) ? max : undefined; }, distinct: function(values) { var map = {}, count = 0, i = -1, n = values.length, value; - while (++i < n) if (!((value = values[i]) in map)) map[value] = ++count; + while (++i < n){ if (!((value = values[i]) in map)){ map[value] = ++count; } } return count; }, median: function(values) { - return quantile(values.sort(ascending), .5); + return quantile(values.sort(ascending), 0.5); } }; diff --git a/lib/cube/server.js b/lib/cube/server.js index eabbf400..e61c6e79 100644 --- a/lib/cube/server.js +++ b/lib/cube/server.js @@ -1,14 +1,38 @@ -var util = require("util"), - url = require("url"), - http = require("http"), - dgram = require("dgram"), - websocket = require("websocket"), - websprocket = require("websocket-server"), - static = require("node-static"), - database = require('./database'); +'use strict'; + +// Server -- generic HTTP, UDP and websockets server +// +// Used by the collector to accept new events via HTTP or websockets +// Used by the evaluator to serve pages over HTTP, and the continuously-updating +// metrics stream over websockets +// +// holds +// * the primary and secondary websockets connections +// * the HTTP listener connection +// * the MongoDB connection +// * the UDP listener connection +// + +var util = require("util"), + url = require("url"), + http = require("http"), + dgram = require("dgram"), + websocket = require("websocket"), + websprocket = require("websocket-server"), + file_server = require("node-static"), + authentication = require("./authentication"), + event = require("./event"), + metalog = require("./metalog"), + Db = require("./db"), + config = require("./config"); + +// Don't crash on errors. +process.on("uncaughtException", function(error) { + metalog.error('server', error); +}); // And then this happened: -websprocket.Connection = require("../../node_modules/websocket-server/lib/ws/connection"); +websprocket.Connection = require("websocket-server/lib/ws/connection"); // Configuration for WebSocket requests. var wsOptions = { @@ -23,35 +47,56 @@ var wsOptions = { closeTimeout: 5000 }; -module.exports = function(options) { - - // Don't crash on errors. - process.on("uncaughtException", function(error) { - util.log("uncaught exception: " + error); - util.log(error.stack); - }); +module.exports = function(options, db) { + config.load(options); var server = {}, primary = http.createServer(), secondary = websprocket.createServer(), - file = new static.Server("static"), - meta, + file = new file_server.Server("static"), + udp, endpoints = {ws: [], http: []}, - id = 0; + id = 0, + authenticator; + + // allows dependency injection from test_helper + if (! db) db = new Db(); secondary.server = primary; + var uses = [] //will be used for registrating anything external, particularly endpoints. + + function is_sec_ws_initiation(request){ + return ("sec-websocket-version" in request.headers); + } + function is_ws_initiation(request){ + return (request.method === "GET" && + (/^websocket$/i).test(request.headers.upgrade) && + (/^upgrade$/i).test(request.headers.connection) ); + } + // Register primary WebSocket listener with fallback. primary.on("upgrade", function(request, socket, head) { - if ("sec-websocket-version" in request.headers) { - request = new websocket.request(socket, request, wsOptions); - request.readHandshake(); - connect(request.accept(request.requestedProtocols[0], request.origin), request.httpRequest); - } else if (request.method === "GET" - && /^websocket$/i.test(request.headers.upgrade) - && /^upgrade$/i.test(request.headers.connection)) { - new websprocket.Connection(secondary.manager, secondary.options, request, socket, head); + function auth_ok(perms) { + if (is_sec_ws_initiation(request)) { + request = new websocket.request(socket, request, wsOptions); + request.readHandshake(); + connect(request.accept(request.requestedProtocols[0], request.origin), request.httpRequest); + } else if (is_ws_initiation(request)) { + new websprocket.Connection(secondary.manager, secondary.options, request, socket, head); + } } + function auth_no(perms) { + if (is_sec_ws_initiation(request)) { + request = new websocket.request(socket, request, wsOptions); + request.readHandshake(); + request.reject(); + } else if (is_ws_initiation(request)) { + res = 'HTTP/1.1 403 Forbidden\r\nConnection: close'; + socket.end(res + '\r\n\r\n', 'ascii'); + } + } + return authenticator.check(request, auth_ok, auth_no); }); // Register secondary WebSocket listener. @@ -63,63 +108,64 @@ module.exports = function(options) { }); function connect(connection, request) { + // save auth from connection requesta + var authorization = request.authorized; + + function connection_callback(response) { + metalog.dump_trace('resp', response); delete response._trace; + connection.sendUTF(JSON.stringify(response)); + } // Forward messages to the appropriate endpoint, or close the connection. for (var i = -1, n = endpoints.ws.length, e; ++i < n;) { if ((e = endpoints.ws[i]).match(request.url)) { - var callback = function(response) { - connection.sendUTF(JSON.stringify(response)); - }; - - callback.id = ++id; + connection_callback.id = ++id; // Listen for socket disconnect. - if (e.dispatch.close) connection.socket.on("end", function() { - e.dispatch.close(callback); - }); + if (e.dispatch.close) { + connection.socket.on("end", function() { + e.dispatch.close(connection_callback); + }); + connection.socket.on("close", function() { + e.dispatch.close(connection_callback); + }); + } connection.on("message", function(message) { - e.dispatch(JSON.parse(message.utf8Data || message), callback); - }); - - meta({ - type: "cube_request", - time: Date.now(), - data: { - ip: connection.remoteAddress, - path: request.url, - method: "WebSocket" - } + // parse, staple the authorization on, then process + var payload = JSON.parse(message.utf8Data || message); + payload.authorized = authorization; + metalog.trace('req', payload); + e.dispatch(payload, connection_callback); }); + metalog.event('connect', { method: 'ws', ip: connection.remoteAddress, path: request.url}, 'minor'); return; } } - connection.close(); } // Register HTTP listener. primary.on("request", function(request, response) { var u = url.parse(request.url); + metalog.trace('http', request); + + function auth_ok(perms) { + metalog.trace('auth_ok', request, { method: request.method, ip: request.connection.remoteAddress, path: u.pathname }); + e.dispatch(request, response); + } + function auth_no(reason) { + metalog.dump_trace('auth_no', request, { method: request.method, ip: request.connection.remoteAddress, path: u.pathname }); + response.writeHead(403, {"Content-Type": "text/plain"}); + response.end("403 Forbidden"); + } // Forward messages to the appropriate endpoint, or 404. for (var i = -1, n = endpoints.http.length, e; ++i < n;) { if ((e = endpoints.http[i]).match(u.pathname, request.method)) { - e.dispatch(request, response); - - meta({ - type: "cube_request", - time: Date.now(), - data: { - ip: request.connection.remoteAddress, - path: u.pathname, - method: request.method - } - }); - - return; + return authenticator.check(request, auth_ok, auth_no); } } @@ -127,8 +173,11 @@ module.exports = function(options) { request.on("end", function() { file.serve(request, response, function(error) { if (error) { + metalog.error('req_file', error, { ip: request.connection.remoteAddress, path: u.pathname }); response.writeHead(error.status, {"Content-Type": "text/plain"}); response.end(error.status + ""); + } else { + metalog.trace('req_file', request, { ip: request.connection.remoteAddress, path: u.pathname }); } }); }); @@ -139,24 +188,50 @@ module.exports = function(options) { } }); - server.start = function() { - // Connect to mongodb. - util.log("starting mongodb client"); - database.open(options, function (error, db) { - if (error) throw error; - server.register(db, endpoints); - meta = require("./event").putter(db); - util.log("starting http server on port " + options["http-port"]); - primary.listen(options["http-port"]); - if (endpoints.udp) { - util.log("starting udp server on port " + options["udp-port"]); - var udp = dgram.createSocket("udp4"); + server.use = function(endpointAdder) { + uses.push(endpointAdder); + return server; + } + + server.start = function(server_start_cb) { + db.open(function(error, db){ + handle(error); + ready(db); + }); + + // Start the server! + function ready(db) { + metalog.putter = event.putter(db); + uses.forEach(function(adder){ adder(db, endpoints); }); + authenticator = authentication.authenticator(options["authenticator"], db); + metalog.event('start_http', { port: options["http-port"], host: options["http-host"] }); + primary.listen(options["http-port"], options["http-host"]); + if (endpoints.udp && options["udp-port"] !== undefined) { + metalog.event('start_udp', { port: options["udp-port"], host: options["udp-host"] }); + udp = dgram.createSocket("udp4"); udp.on("message", function(message) { endpoints.udp(JSON.parse(message.toString("utf8")), ignore); }); - udp.bind(options["udp-port"]); + udp.bind(options["udp-port"], options["udp-host"]); } - }); + if (server_start_cb) server_start_cb(null, options); + } + }; + + primary.on( "close", function(){ metalog.info('http_close'); }); + secondary.on("close", function(){ metalog.info('ws_close' ); }); + function try_close(name, obj){ if (obj){ + try { + metalog.info(name+'_stopping', config); + obj.close( function(){ metalog.info(name+'_stop'); } ); + } catch(error){} + } } + server.stop = function(cb){ + try_close('http', primary); // stop serving + try_close('ws', secondary); + try_close('udp', udp); + setTimeout(function(){try_close('mongo', db);}, 50); // stop db'ing + if (cb) cb(); }; return server; @@ -165,3 +240,9 @@ module.exports = function(options) { function ignore() { // Responses for UDP are ignored; there's nowhere for them to go! } + +function handle(error) { + if (!error) return; + metalog.error('server', error); + throw error; +} diff --git a/lib/cube/tiers.js b/lib/cube/tiers.js index f498220a..a78eae37 100644 --- a/lib/cube/tiers.js +++ b/lib/cube/tiers.js @@ -1,3 +1,5 @@ +'use strict'; + var tiers = module.exports = {}; var second = 1000, @@ -10,42 +12,67 @@ var second = 1000, tiers[second10] = { key: second10, floor: function(d) { return new Date(Math.floor(d / second10) * second10); }, - ceil: tier_ceil, - step: function(d) { return new Date(+d + second10); } + bin: function(d) { return new Date(Math.floor(d / second10) * second10); }, + ceil: tier_ceil, + step: function(d) { return new Date(+d + second10); } }; tiers[minute] = { key: minute, floor: function(d) { return new Date(Math.floor(d / minute) * minute); }, - ceil: tier_ceil, - step: function(d) { return new Date(+d + minute); } + bin: function(d) { return new Date(Math.floor(d / minute) * minute); }, + ceil: tier_ceil, + step: function(d) { return new Date(+d + minute); }, + next: tiers[second10], + size: function() { return 6; } }; tiers[minute5] = { key: minute5, floor: function(d) { return new Date(Math.floor(d / minute5) * minute5); }, - ceil: tier_ceil, - step: function(d) { return new Date(+d + minute5); } + bin: function(d) { return new Date(Math.floor(d / minute5) * minute5); }, + ceil: tier_ceil, + step: function(d) { return new Date(+d + minute5); }, + next: tiers[minute], + size: function() { return 5; } }; tiers[hour] = { key: hour, floor: function(d) { return new Date(Math.floor(d / hour) * hour); }, - ceil: tier_ceil, - step: function(d) { return new Date(+d + hour); }, - next: tiers[minute5], - size: function() { return 12; } + bin: function(d) { return new Date(Math.floor(d / hour) * hour); }, + ceil: tier_ceil, + step: function(d) { return new Date(+d + hour); }, + next: tiers[minute5], + size: function() { return 12; } }; tiers[day] = { key: day, floor: function(d) { return new Date(Math.floor(d / day) * day); }, - ceil: tier_ceil, - step: function(d) { return new Date(+d + day); }, - next: tiers[hour], - size: function() { return 24; } + bin: function(d) { return new Date(Math.floor(d / day) * day); }, + ceil: tier_ceil, + step: function(d) { return new Date(+d + day); }, + next: tiers[hour], + size: function() { return 24; } }; +Object.defineProperty(tiers, "bins", { + enumberable: false, + value: function(d){ + var bins = {}; + for(var bin in tiers) bins[tiers[bin].key] = tiers[bin].bin(d); + return bins; + } +}); + +Object.defineProperty(tiers, "units", { + enumberable: false, + value: { second: second, second10: second10, minute: minute, minute5: minute5, hour: hour, day: day } +}); + + + function tier_ceil(date) { return this.step(this.floor(new Date(date - 1))); } diff --git a/lib/cube/types.js b/lib/cube/types.js deleted file mode 100644 index 251f5e8e..00000000 --- a/lib/cube/types.js +++ /dev/null @@ -1,37 +0,0 @@ -// Much like db.collection, but caches the result for both events and metrics. -// Also, this is synchronous, since we are opening a collection unsafely. -var types = module.exports = function(db) { - var collections = {}; - return function(type) { - var collection = collections[type]; - if (!collection) { - collection = collections[type] = {}; - db.collection(type + "_events", function(error, events) { - collection.events = events; - }); - db.collection(type + "_metrics", function(error, metrics) { - collection.metrics = metrics; - }); - } - return collection; - }; -}; - -var eventRe = /_events$/; - -types.getter = function(db) { - return function(request, callback) { - db.collectionNames(function(error, names) { - handle(error); - callback(names - .map(function(d) { return d.name.split(".")[1]; }) - .filter(function(d) { return eventRe.test(d); }) - .map(function(d) { return d.substring(0, d.length - 7); }) - .sort()); - }); - }; -}; - -function handle(error) { - if (error) throw error; -} diff --git a/lib/cube/visualizer.js b/lib/cube/visualizer.js new file mode 100644 index 00000000..3fab43a2 --- /dev/null +++ b/lib/cube/visualizer.js @@ -0,0 +1,137 @@ +'use strict'; + +var url = require("url"), + path = require("path"), + endpoint = require("./endpoint"), + metalog = require('./metalog'), + parser = require("./metric-expression"); + +exports.register = function(db, endpoints) { + endpoints.ws.push( + endpoint(/^\/boards\/[a-z0-9\-_]+(\/edit)?$/i, viewBoard(db)) + ); +}; + +function viewBoard(db) { + var boards, + boardsByCallback = {}, + callbacksByBoard = {}; + + function dispatch(request, callback) { + if (request.type != 'ping') metalog.info("viz_req", { is: request.type, bd: request.id, pc: callback.id }); + request.id = require('mongodb').ObjectID(request.id); + + db.collection("boards", function(error, collection) { + boards = collection; + switch (request.type) { + case "load": load(request, callback); break; + case "add": add(request, callback); break; + case "edit": case "move": move(request, callback); break; + case "remove": remove(request, callback); break; + default: callback({type: "error", status: 400}); break; + } + }); + } + + function check_authorization(request, action){ + if (request.authorized.admin) return true; + metalog.info('viz_auth_no', { action: action, u: request.authorized }); + return false; + } + + function add(request, callback) { + if (! check_authorization(request, 'add')) return; + + var boardId = request.id, + callbacks = callbacksByBoard[boardId].filter(function(c) { return c.id != callback.id; }); + boards.update({_id: boardId}, {$push: {pieces: request.piece}}); + if (callbacks.length) emit(callbacks, {type: "add", piece: request.piece}); + } + + function move(request, callback) { + if (! check_authorization(request, 'move')) return; + + var boardId = request.id, + callbacks = callbacksByBoard[boardId].filter(function(c) { return c.id != callback.id; }), + response = callbacksByBoard[boardId].filter(function(c) { return c.id == callback.id; }), + error; + boards.update({_id: boardId, "pieces.id": request.piece.id}, {$set: {"pieces.$": request.piece}}); + if (request.piece.query) { + try { parser.parse(request.piece.query); } + catch (e) { error = e.message + ' Line: ' + e.line + ', Column: ' + e.column; } + } + if (error) emit(response, { type: 'error', piece: request.piece, error: error }); + if (callbacks.length && !error) emit(callbacks, {type: request.type, piece: request.piece}); + } + + function remove(request, callback) { + if (! check_authorization(request, 'remove')) return; + + var boardId = request.id, + callbacks = callbacksByBoard[boardId].filter(function(c) { return c.id != callback.id; }); + boards.update({_id: boardId}, {$pull: {pieces: {id: request.piece.id}}}); + if (callbacks.length) emit(callbacks, {type: "remove", piece: {id: request.piece.id}}); + } + + function load(request, callback) { + var boardId = boardsByCallback[callback.id], + callbacks; + + // If callback was previously viewing to a different board, remove it. + if (boardId) { + callbacks = callbacksByBoard[boardId]; + callbacks.splice(callbacks.indexOf(callback), 1); + if (callbacks.length) emit(callbacks, {type: "view", count: callbacks.length}); + else delete callbacksByBoard[boardId]; + } + + // Register that we are now viewing the new board. + boardsByCallback[callback.id] = boardId = request.id; + + // If this board has other viewers, notify them. + if (boardId in callbacksByBoard) { + callbacks = callbacksByBoard[boardId]; + callbacks.push(callback); + emit(callbacks, {type: "view", count: callbacks.length}); + } else { + callbacks = callbacksByBoard[boardId] = [callback]; + } + + // Asynchronously load the requested board. + boards.findOne({_id: boardId}, function(error, board) { + if (board !== null) { + if (board.pieces) board.pieces.forEach(function(piece) { + callback({type: "add", piece: piece}); + }); + } else { + callback({type: "error", status: 404}); + } + }); + } + + dispatch.close = function(callback) { + var boardId = boardsByCallback[callback.id], + callbacks; + + // If callback was viewing, remove it. + if (boardId) { + callbacks = callbacksByBoard[boardId]; + callbacks.splice(callbacks.indexOf(callback), 1); + if (callbacks.length) emit(callbacks, {type: "view", count: callbacks.length}); + else delete callbacksByBoard[boardId]; + delete boardsByCallback[callback.id]; + } + }; + + return dispatch; +} + +function resolve(file) { + return path.join(__dirname, "../client", file); +} + +function emit(callbacks, event) { + callbacks.forEach(function(callback) { + callback(event); + }); +} diff --git a/lib/cube/warmer.js b/lib/cube/warmer.js new file mode 100644 index 00000000..9354f2b9 --- /dev/null +++ b/lib/cube/warmer.js @@ -0,0 +1,72 @@ +'use strict'; + +var metric = require('./metric'), + tiers = require('./tiers'), + metalog = require('./metalog'), + db = new (require('./db'))(), + config = require('./config'); + +module.exports = function(options) { + config.load(options); + + var calculate_metric, tier, timeout, + horizons = config.horizons, + warmer = config.warmer; + + function fetch_metrics(callback){ + var expressions = []; + + db.collection("boards", function(error, collection){ + collection.find({}, {pieces: 1}, function(error, cursor) { + if (error) throw error; + cursor.each(function(error, row) { + if (error) throw error; + if (row) { + expressions.splice.apply(expressions, [0, 0].concat((row.pieces||[]) + .map(function(piece){ return piece.query; }) + .filter(function(expression){ return expression && !(expression in expressions); }) + )); + } else { + callback(expressions); + } + }); + }); + }); + } + + function process_metrics(expressions){ + expressions.forEach(function(expression){ + var stop = new Date(), + start = tier.step(tier.floor(new Date(stop - horizons.calculation))); + + metalog.info('cube_warm', {is: 'warm_metric', metric: {query: expressions}, start: start, stop: stop }); + + // fake metrics request + calculate_metric({ step: tier.key, expression: expression, start: start, stop: stop }, noop); + }); + timeout = setTimeout(function(){ fetch_metrics(process_metrics); }, warmer['warmer-interval']); + } + + function noop(){}; + + return { + start: function(){ + tier = tiers[warmer['warmer-tier'].toString()]; + + if(typeof tier === "undefined") throw new Error("Undefined warmer tier configured: " + warmer['warmer-tier']); + + metalog.event("cube_life", { is: 'start_warmer', options: config }); + + db.open(function(error) { + if (error) throw error; + calculate_metric = metric.getter(db); + fetch_metrics(process_metrics); + }); + }, + + stop: function(){ + metalog.event("cube_life", { is: 'stop_warmer' }); + clearTimeout(timeout); + } + }; +}; diff --git a/package.json b/package.json index 3991731e..ad782ebf 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,20 @@ "main": "./lib/cube", "dependencies": { "mongodb": "~1.3.18", - "node-static": "0.6.5", + "node-static": "~0.7.0", "pegjs": "0.7.0", "vows": "0.7.0", "websocket": "1.0.8", - "websocket-server": "1.4.04" + "websocket-server": "1.4.04", + "cookies": "0.3.1", + "bcrypt": "0.7.7", + "underscore": "1.3.3", + "jake": "0.5.6", + "queue-async": "~1.0.4" + }, + "scripts": { + "preinstall": "npm install mongodb --mongodb:native", + "test": "NODE_ENV=test vows --isolate --spec", + "remove_expired_metrics": "jake db:metrics:remove_expired" } } diff --git a/static/semicolon.js b/static/semicolon.js new file mode 100644 index 00000000..1c8a0e79 --- /dev/null +++ b/static/semicolon.js @@ -0,0 +1 @@ +; \ No newline at end of file diff --git a/test/authentication-test.js b/test/authentication-test.js new file mode 100644 index 00000000..dfa65d7b --- /dev/null +++ b/test/authentication-test.js @@ -0,0 +1,169 @@ +'use strict'; + +var vows = require("vows"), + assert = require("assert"), + test_helper = require('./test_helper'), + metalog = require("../lib/cube/metalog"), + authentication = require("../lib/cube/authentication"); + +var suite = vows.describe("authentication"); +suite.options.error = true; + +// +// Test Macros +// + +function successful_auth(req) { + return function(authenticator){ + var that = this; + that.req = req; + authenticator.check(req, + function(arg){ that.callback(null, arg); }, + function(arg){ that.callback(new Error("Auth failed but should have succeeded")); }); + }; +} + +function failed_auth(req) { + return function(authenticator){ + var that = this; + that.req = req; + authenticator.check(req, + function(arg){ that.callback("Auth succeeded but should have failed"); }, + function(arg){ that.callback(null, arg); } ); + }; +} + +// as I'm sure you know: boss_hogg and luke have write access (luke doubly so); +// roscoe can't do much but sit around; and it's as if coy & vance never existed +var test_users = [ + { _id: "boss_hogg", tokens: [{ uid: 'boss_hogg_tok', admin: true, hashed_secret: "$2a$10$3u.CU4pJLnPDM7VwhJtbyuLwGBiOwpQ42q0wFQEDoJZtirAgIrBI6"}] }, + { _id: "luke", tokens: [{ uid: 'luke_tok', admin: true, hashed_secret: "$2a$10$K5NpLr3qrhxsUBW0iCw8iegQzgEINdWDk2n1BrTYe1x1Ay4dU2PlG"}, + { uid: 'luke_2_tok', admin: true, hashed_secret: "$2a$10$0I6KVPSzUIXdlxdY7qPTF.dde4tjPGRahYcja96Fz6ZaakEfdnNGO"}] }, + { _id: "roscoe", tokens: [{ uid: 'roscoe_tok', admin: false, hashed_secret: "$2a$10$BKIqJukrlFtbjFeeLCnEvOwHdLMLDt61iyfMRLiEf9lNeWKD.djrm"}] }, + { _id: "vance", tokens: [] } +]; +var dummy_token = "token_in_cookie"; + +function dummy_request(username, token){ + return({ headers: { cookie: authentication.gen_cookie("_cube_session", username+"_tok", token || dummy_token) } }); +} + +suite.addBatch(test_helper.batch({ + mongo_cookie: { + topic: function(test_db){ test_db.using_objects("test_users", test_users, this); }, + "": { + topic: function(test_db){ + return authentication.authenticator("mongo_cookie", test_db); + }, + "authenticates": { + "users with good tokens": { + topic: successful_auth(dummy_request("boss_hogg")), + '': function(result){ assert.deepEqual(this.req.authorized, { uid: 'boss_hogg_tok', admin: true }); } + }, + "users with tokens, even if there are many": { + "a": { + topic: successful_auth(dummy_request("luke")), + '': function(result){ assert.deepEqual(this.req.authorized, { uid: 'luke_tok', admin: true }); } + }, + "b": { + topic: successful_auth(dummy_request("luke_2")), + '': function(result){ assert.deepEqual(this.req.authorized, { uid: 'luke_2_tok', admin: true }); } + } + } + }, + "request.authorized": { + "": { + topic: successful_auth(dummy_request("boss_hogg")), + 'is stapled to the request object': function(result){ + assert.isObject(this.req.authorized); + assert.deepEqual(this.req.authorized, { uid: 'boss_hogg_tok', admin: true }); + }, + 'is returned as callback param': function(result){ + assert.deepEqual(result, { uid: 'boss_hogg_tok', admin: true }); + } + }, + "user with write access": { + topic: successful_auth(dummy_request("boss_hogg")), + 'request.authorized.admin is true': function(result){ + assert.isTrue(this.req.authorized.admin); + } + }, + "user with read-only access": { + topic: successful_auth(dummy_request("roscoe")), + 'authenticates': function(result){ + assert.deepEqual(this.req.authorized, { uid: 'roscoe_tok', admin: false }); }, + 'request.authorized.admin is false': function(result){ + assert.isFalse(this.req.authorized.admin); + } + } + }, + "does not allow": { + "bad token": { + topic: failed_auth(dummy_request("boss_hogg", "bad_token")), + "invokes auth_no callback with reason": function(reason){ + assert.equal(reason, 'bad_token'); + }, + "does not authorize request": function(reason){ assert.isUndefined(this.req.authorized); } + }, + "no token in request": { + topic: failed_auth({ headers: { cookie: "" } }), + "invokes auth_no callback with reason": function(reason){ + assert.equal(reason, 'no_token_in_request'); + }, + "does not authorize request": function(reason){ assert.isUndefined(this.req.authorized); } + }, + "user there, no auth record": { + topic: failed_auth(dummy_request("vance")), + "invokes auth_no callback with reason": function(reason){ + assert.equal(reason, 'missing_user'); + }, + "does not authorize request": function(reason){ assert.isUndefined(this.req.authorized); } + }, + "missing user": { + topic: failed_auth(dummy_request("coy")), + "invokes auth_no callback with reason": function(reason){ + assert.equal(reason, 'missing_user'); + }, + "does not authorize request": function(reason){ assert.isUndefined(this.req.authorized); } + } + } + } + }, + + "allow_all": { + topic: function(test_db){ + return authentication.authenticator("allow_all"); }, + "calls the auth_ok callback immediately" : { + topic: successful_auth({type: "allow_all auth"}), + 'decorates the request object': function(result){ + assert.isObject(this.req.authorized); + }, + 'returns the authorization hash': function(result){ + assert.deepEqual(result, { admin: true }); + }, + 'authorizes writes': function(result){ + assert.deepEqual(this.req.authorized, { admin: true }); + } + } + }, + + "read_only": { + topic: function(test_db){ + return authentication.authenticator("read_only"); }, + "calls the auth_ok callback immediately" : { + topic: successful_auth({type: "read_only auth"}), + 'decorates the request object': function(result){ + assert.isObject(this.req.authorized); + }, + 'returns the authorization hash': function(result){ + assert.deepEqual(result, { admin: false }); + }, + 'authorizes writes': function(result){ + assert.deepEqual(this.req.authorized, { admin: false }); + } + } + } + +})); + +suite['export'](module); diff --git a/test/collector-test.js b/test/collector-test.js index 2a33a78d..5b1b599a 100644 --- a/test/collector-test.js +++ b/test/collector-test.js @@ -1,34 +1,26 @@ -var vows = require("vows"), - assert = require("assert"), - cube = require("../"), - test = require("./helpers"); +'use strict'; -var suite = vows.describe("collector"); - -var server = cube.server(test.config), - port = test.config["http-port"]; +var vows = require("vows"), + assert = require("assert"), + test_helper = require("./test_helper"), + cube = require("../"); -console.log('collector port %s', port); - -server.register = cube.collector.register; +var suite = vows.describe("collector"); -server.start(); +suite.addBatch( + test_helper.with_server('collector', cube.collector.register, { -suite.addBatch(test.batch({ "POST /event/put with invalid JSON": { - topic: test.request({method: "POST", port: port, path: "/1.0/event/put"}, "This ain't JSON.\n"), + topic: test_helper.request({method: "POST", path: "/1.0/event/put"}, "This ain't JSON.\n"), "responds with status 400": function(response) { assert.equal(response.statusCode, 400); assert.deepEqual(JSON.parse(response.body), {error: "SyntaxError: Unexpected token T"}); } - } -})); - -suite.addBatch(test.batch({ + }, "POST /event/put with a JSON object": { - topic: test.request({method: "POST", port: port, path: "/1.0/event/put"}, JSON.stringify({ + topic: test_helper.request({method: "POST", path: "/1.0/event/put"}, JSON.stringify({ type: "test", - time: new Date(), + time: new Date, data: { foo: "bar" } @@ -37,14 +29,11 @@ suite.addBatch(test.batch({ assert.equal(response.statusCode, 400); assert.deepEqual(JSON.parse(response.body), {error: "TypeError: Object # has no method 'forEach'"}); } - } -})); - -suite.addBatch(test.batch({ + }, "POST /event/put with a JSON array": { - topic: test.request({method: "POST", port: port, path: "/1.0/event/put"}, JSON.stringify([{ + topic: test_helper.request({method: "POST", path: "/1.0/event/put"}, JSON.stringify([{ type: "test", - time: new Date(), + time: new Date, data: { foo: "bar" } @@ -53,22 +42,16 @@ suite.addBatch(test.batch({ assert.equal(response.statusCode, 200); assert.deepEqual(JSON.parse(response.body), {}); } - } -})); - -suite.addBatch(test.batch({ + }, "POST /event/put with a JSON number": { - topic: test.request({method: "POST", port: port, path: "/1.0/event/put"}, JSON.stringify(42)), + topic: test_helper.request({method: "POST", path: "/1.0/event/put"}, JSON.stringify(42)), "responds with status 400": function(response) { assert.equal(response.statusCode, 400); assert.deepEqual(JSON.parse(response.body), {error: "TypeError: Object 42 has no method 'forEach'"}); } - } -})); - -suite.addBatch(test.batch({ + }, "POST /event/put without an associated time": { - topic: test.request({method: "POST", port: port, path: "/1.0/event/put"}, JSON.stringify([{ + topic: test_helper.request({method: "POST", path: "/1.0/event/put"}, JSON.stringify([{ type: "test", data: { foo: "bar" @@ -81,4 +64,4 @@ suite.addBatch(test.batch({ } })); -suite.export(module); +suite['export'](module); diff --git a/test/evaluator-test.js b/test/evaluator-test.js new file mode 100644 index 00000000..5dc6b1fd --- /dev/null +++ b/test/evaluator-test.js @@ -0,0 +1,27 @@ +'use strict'; + +var vows = require("vows"), + assert = require("assert"), + test_helper = require("./test_helper"), + cube = require("../"); + +var suite = vows.describe("evaluator"); + +function frontend_components() { + cube.evaluator.register.apply(this, arguments); + cube.visualizer.register.apply(this, arguments); +} + +suite.addBatch( + test_helper.with_server('evaluator', frontend_components, { + + "POST /event/put with invalid JSON": { + topic: test_helper.request({method: "GET", path: "/1.0/event/get"}, "This ain't JSON.\n"), + "responds with status 400": function(response) { + assert.equal(response.statusCode, 400); + assert.deepEqual(JSON.parse(response.body), {error: "invalid expression", message: {}}); + } + } +})); + +suite['export'](module); diff --git a/test/event-expression-test.js b/test/event-expression-test.js index 9f12a79b..73f15758 100644 --- a/test/event-expression-test.js +++ b/test/event-expression-test.js @@ -1,6 +1,8 @@ -var vows = require("vows"), - assert = require("assert"), - parser = require("../lib/cube/event-expression"); +'use strict'; + +var vows = require("vows"), + assert = require("assert"), + parser = require("../lib/cube/event-expression"); var suite = vows.describe("event-expression"); @@ -240,4 +242,4 @@ suite.addBatch({ }); -suite.export(module); +suite['export'](module); diff --git a/test/event-test.js b/test/event-test.js new file mode 100644 index 00000000..70cf70f5 --- /dev/null +++ b/test/event-test.js @@ -0,0 +1,75 @@ +'use strict'; + +var vows = require("vows"), + assert = require("assert"), + test_helper = require("./test_helper"), + Event = require("../lib/cube/models/event"), + event = require("../lib/cube/event"), + config = require('../lib/cube/config'); + +var suite = vows.describe("event"); + +var ice_cubes_good_day = Date.UTC(1992, 1, 20, 1, 8, 7), + fuck_wit_dre_day = Date.UTC(1993, 2, 18, 8, 44, 54); + +suite.addBatch(test_helper.batch({ + topic: function(test_db) { + return this.putter = event.putter(test_db); + }, + 'invalidates': { + topic: function(putter){ + var _this = this; + putter((new Event('test', ice_cubes_good_day, {value: 3})).to_request(), function(){ + putter((new Event('test', fuck_wit_dre_day, {value: 3})).to_request(), _this.callback);}); + }, + 'correct tiers': function(a,b){ + var ts = this.putter.invalidator().tsets; + assert.deepEqual(ts, { 'test': { + 10e3: [new Date('1992-02-20T01:08:00Z'), new Date('1993-03-18T08:44:50Z') ], + 60e3: [new Date('1992-02-20T01:08:00Z'), new Date('1993-03-18T08:44:00Z') ], + 300e3: [new Date('1992-02-20T01:05:00Z'), new Date('1993-03-18T08:40:00Z') ], + 3600e3: [new Date('1992-02-20T01:00:00Z'), new Date('1993-03-18T08:00:00Z') ], + 86400e3: [new Date('1992-02-20T00:00:00Z'), new Date('1993-03-18T00:00:00Z') ] + }}); + } + }, + 'callback': { + topic: function(putter){ + putter((new Event('test', fuck_wit_dre_day, {value: 3})).to_request(), this.callback); + }, + 'no error arg': function(arg1, arg2){ + assert.instanceOf(arg1, Event); + assert.typeOf(arg2, 'undefined'); + } + }, + teardown: function(putter){ putter.stop(this.callback); } +})); + +suite.addBatch(test_helper.batch({ + topic: function(test_db) { + var horizon = new Date() - fuck_wit_dre_day + (1000 * 60); + + // Temporarily override horizons settings + this.oldHorizons = config.horizons; + config.horizons = { invalidation: horizon }; + return event.putter(test_db.db); + }, + 'events past invalidation horizon': { + topic: function(putter){ + var _this = this, + event = new Event('test', ice_cubes_good_day, {value: 3}); + this.ret = putter(event.to_request(), this.callback); + }, + 'should error': function(error, response){ + assert.deepEqual(error, {error: "event before invalidation horizon"}); + }, + 'should return -1': function(error, response){ + assert.equal(this.ret, -1); + } + }, + teardown: function() { + config.horizons = this.oldHorizons; + } +})); + +suite['export'](module); diff --git a/test/helpers.js b/test/helpers.js deleted file mode 100644 index 19ca6eb8..00000000 --- a/test/helpers.js +++ /dev/null @@ -1,55 +0,0 @@ -var database = require("../lib/cube/database"), - util = require("util"), - http = require("http"); - -var config = exports.config = require('./test-config'); - -exports.batch = function(batch) { - return { - "": { - topic: function() { - var cb = this.callback; - database.open(config, function(error, db) { - if (error) { - return cb(error); - } - var collectionsRemaining = 2; - db.dropCollection("test_events", collectionReady); - db.dropCollection("test_metrics", collectionReady); - function collectionReady() { - if (!--collectionsRemaining) { - cb(null, {db: db}); - } - } - }); - }, - "": batch, - teardown: function(test) { - test.db.close(); - } - } - }; -}; - -exports.request = function(options, data) { - return function() { - var cb = this.callback; - - options.host = "localhost"; - - var request = http.request(options, function(response) { - response.body = ""; - response.setEncoding("utf8"); - response.on("data", function(chunk) { response.body += chunk; }); - response.on("end", function() { cb(null, response); }); - }); - - request.on("error", function(e) { cb(e, null); }); - - if (data && data.length > 0) request.write(data); - request.end(); - }; -}; - -// Disable logging for tests. -util.log = function() {}; diff --git a/test/metalog-test.js b/test/metalog-test.js new file mode 100644 index 00000000..4f36b27d --- /dev/null +++ b/test/metalog-test.js @@ -0,0 +1,124 @@ +'use strict'; + +var vows = require("vows"), + assert = require("assert"), + test_helper = require('./test_helper'); + +var suite = vows.describe("metalog"); + +suite.with_log = function(batch){ + suite.addBatch({ + '':{ + topic: function(){ + var metalog = require("../lib/cube/metalog"); + var logged = this.logged = { infoed: [], minored: [], putted: [] }; + this.original = { send_events: metalog.send_events, putter: metalog.putter, info: metalog.loggers.info, minor: metalog.loggers.minor }; + metalog.send_events = true; + metalog.loggers.info = function(line){ logged.infoed.push(line); }; + metalog.loggers.minor = function(line){ logged.minored.push(line); }; + metalog.putter = function(line){ logged.putted.push(line); }; + return metalog; + }, + metalog: batch, + teardown: function(metalog){ + metalog.loggers.info = this.original.info; + metalog.loggers.minor = this.original.minor; + metalog.putter = this.original.putter; + } + } + }); + return suite; +}; + +suite.with_log({ + '.info': { + 'logs record to metalog.logers.info': function(metalog){ + metalog.info('reactor_level', { criticality: 7, cores: 'leaking' }); + assert.equal(this.logged.infoed.pop(), 'reactor_level\t{"criticality":7,"cores":"leaking"}'); + assert.deepEqual(this.logged, { infoed: [], minored: [], putted: [] }); + } + } +}).with_log({ + '.minor': { + 'logs record to metalog.loggers.minor': function(metalog){ + metalog.minor('reactor_level', { modacity: 3 }); + assert.equal(this.logged.minored.pop(), 'reactor_level\t{"modacity":3}'); + assert.deepEqual(this.logged, { infoed: [], minored: [], putted: [] }); + } + } +}).with_log({ + '.event': { + 'with send_events=true': { + topic: function(metalog){ + metalog.send_events = true; + metalog.event('reactor_level', { criticality: 9, hemiconducers: 'relucting' }); + return metalog; + }, + 'logs record to metalog.info by default': function(metalog){ + assert.equal(this.logged.infoed.pop(), 'reactor_level\t{"criticality":9,"hemiconducers":"relucting"}'); + }, + 'writes an event to cube itself': function(metalog){ + var event = this.logged.putted.pop(); + event.time = 'whatever'; + assert.deepEqual(event, { + data: { hemiconducers: 'relucting', criticality: 9, at: "reactor_level" }, + type: 'cube', + time: 'whatever' + }); + } + } + } +}).with_log({ + '.event': { + 'with send_events=false': { + topic: function(metalog){ + metalog.send_events = false; + metalog.event('reactor_level', { criticality: 10, hemiconducers: 'fremulating' }); + return metalog; + }, + 'logs record to metalog.loggers.info': function(metalog){ + assert.equal(this.logged.infoed.pop(), 'reactor_level\t{"criticality":10,"hemiconducers":"fremulating"}'); + }, + 'does not write an event to cube': function(metalog){ + assert.deepEqual(this.logged, { infoed: [], minored: [], putted: [] }); + } + } + } +// }).with_log({ +// '.event': { +// 'last parameter overrides log target': { +// topic: function(metalog) { +// metalog.send_events = true; +// metalog.event('reactor_level', { criticality: 3, hemiconducers: 'cromulent' }, 'minor'); +// metalog.event('reactor_level', { criticality: 2, hemiconducers: 'whispery' }, 'silent'); +// return metalog; +// }, +// '': function(metalog){ +// assert.equal(this.logged.minored.pop(), 'reactor_level\t{"criticality":3,"hemiconducers":"cromulent"}'); +// assert.equal(this.logged.putted.pop().data.criticality, 2); +// assert.equal(this.logged.putted.pop().data.criticality, 3); +// assert.deepEqual(this.logged, { infoed: [], minored: [], putted: [] }); +// } +// } +// } +}); + +function dummy_logger(arg){} + +suite.addBatch({ + 'metalog':{ + topic: function(){ return require("../lib/cube/metalog"); }, + '': { + 'loggers persist across factory invocation': function(metalog){ + metalog.orig_minor = metalog.loggers.minor; + metalog.loggers.minor = dummy_logger; + var ml2 = require("../lib/cube/metalog"); + assert.deepEqual(metalog, ml2); + assert.deepEqual(metalog.loggers.minor, dummy_logger); + assert.notDeepEqual(metalog.loggers.minor, metalog.orig_minor); + }, + teardown: function(metalog){ metalog.loggers.minor = metalog.orig_minor; } + } + }}); + +suite['export'](module); diff --git a/test/metric-expression-test.js b/test/metric-expression-test.js index 0fd39844..a1550aee 100644 --- a/test/metric-expression-test.js +++ b/test/metric-expression-test.js @@ -1,6 +1,8 @@ -var vows = require("vows"), - assert = require("assert"), - parser = require("../lib/cube/metric-expression"); +'use strict'; + +var vows = require("vows"), + assert = require("assert"), + parser = require("../lib/cube/metric-expression"); var suite = vows.describe("metric-expression"); @@ -205,7 +207,7 @@ suite.addBatch({ assert.isUndefined(e.source); }, "computes the specified value expression": function(e) { - assert.equal(e.op(2, 3), 5) + assert.equal(e.op(2, 3), 5); } }, @@ -223,7 +225,7 @@ suite.addBatch({ "a negated unary expression": { topic: parser.parse("-sum(foo)"), "negates the specified value expression": function(e) { - assert.equal(e.value(), -1) + assert.equal(e.value(), -1); }, "has the expected source": function(e) { assert.equal(e.source, "-sum(foo)"); @@ -233,7 +235,7 @@ suite.addBatch({ "constant expressions": { topic: parser.parse("-4"), "has a constant value": function(e) { - assert.equal(e.value(), -4) + assert.equal(e.value(), -4); }, "does not have a source": function(e) { assert.isUndefined(e.source); @@ -300,4 +302,4 @@ suite.addBatch({ }); -suite.export(module); +suite['export'](module); diff --git a/test/metric-test.js b/test/metric-test.js index 4bbac942..c66e3f7b 100644 --- a/test/metric-test.js +++ b/test/metric-test.js @@ -1,85 +1,151 @@ -var vows = require("vows"), - assert = require("assert"), - test = require("./helpers"), - event = require("../lib/cube/event"), - metric = require("../lib/cube/metric"); +'use strict'; + +var util = require("util"), metalog = require('../lib/cube/metalog'); + +var _ = require("underscore"), + vows = require("vows"), + assert = require("assert"), + test_helper = require("./test_helper"), + queuer = require("queue-async"), + tiers = require("../lib/cube/tiers"), + units = tiers.units, + event = require("../lib/cube/event"), + metric = require("../lib/cube/metric"); + +// as a hack to get updates to settle, we need to insert delays. +// if you see heisen-errors in the metrics tests, increase these. +var step_testing_delay = 250, + batch_testing_delay = 500; var suite = vows.describe("metric"); +var nowish = Date.now(), + nowish_floor = tiers[10e3].floor(nowish), + nowish_stop = nowish_floor + 30e3, + thenish = Date.UTC(2011, 6, 18, 0, 0, 0); +var invalid_expression_error = { error: { message: 'Expected "(", "-", "distinct", "max", "median", "min", "sum" or number but "D" found.', column: 1, line: 1, name: 'SyntaxError' }}; + +function gen_date(sec){ + return new Date(thenish + sec * units.second); +} + +var t1 = gen_date(3), t1_10s = new Date(10e3 * Math.floor(t1/10e3)), + t2 = gen_date(35), t2_10s = new Date(10e3 * Math.floor(t2/10e3)); + +function gen_request(attrs){ + var req = { start: t1, stop: t2, step: units.second10, expression: 'max(test(i))'}; + for (var key in attrs){ req[key] = attrs[key]; } + return req; +} + +function assert_invalid_request(req, expected_err) { + return { + topic: function(getter){ this.ret = getter(gen_request(req), this.callback); }, + 'fails': function(err, val){ assert.deepEqual(err, expected_err); }, + 'returns -1': function(err, val){ assert.equal(this.ret, -1); } + }; +} + +function skip(){ // FIXME: remove ------------------------------------------------------------ + var steps = { - 1e4: function(date, n) { return new Date((Math.floor(date / 1e4) + n) * 1e4); }, - 6e4: function(date, n) { return new Date((Math.floor(date / 6e4) + n) * 6e4); }, - 3e5: function(date, n) { return new Date((Math.floor(date / 3e5) + n) * 3e5); }, - 36e5: function(date, n) { return new Date((Math.floor(date / 36e5) + n) * 36e5); }, - 864e5: function(date, n) { return new Date((Math.floor(date / 864e5) + n) * 864e5); } + 10e3: function(date, n) { return new Date((Math.floor(date / units.second10) + n) * units.second10); }, + 60e3: function(date, n) { return new Date((Math.floor(date / units.minute) + n) * units.minute); }, + 300e3: function(date, n) { return new Date((Math.floor(date / units.minute5) + n) * units.minute5); }, + 3600e3: function(date, n) { return new Date((Math.floor(date / units.hour) + n) * units.hour); }, + 86400e3: function(date, n) { return new Date((Math.floor(date / units.day) + n) * units.day); } }; +steps[units.second10].description = "10-second"; +steps[units.minute ].description = "1-minute"; +steps[units.minute5 ].description = "5-minute"; +steps[units.hour ].description = "1-hour"; +steps[units.day ].description = "1-day"; -steps[1e4].description = "10-second"; -steps[6e4].description = "1-minute"; -steps[3e5].description = "5-minute"; -steps[36e5].description = "1-hour"; -steps[864e5].description = "1-day"; - -suite.addBatch(test.batch({ - topic: function(test) { - var putter = event.putter(test.db), - getter = metric.getter(test.db), - callback = this.callback; - - for (var i = 0; i < 2500; i++) { - putter({ - type: "test", - time: new Date(Date.UTC(2011, 6, 18, 0, Math.sqrt(i) - 10)).toISOString(), - data: {i: i} - }); - } +suite.addBatch(test_helper.batch({ + topic: function(test_db) { + var putter = event.putter(test_db), + getter = metric.getter(test_db), + callback = this.callback, + put_queue = queuer(10); + this.putter = putter; - function waitForEvents() { - test.db.collection("test_events").count(function(err,count) { - if (count == 2500) { - callback(null, getter); - } else { - setTimeout(waitForEvents, 10); - } - }); + // Seed the events table with a simple event: a value going from 0 to 2499 + for (var i = 0; i < 2500; i++){ + put_queue.defer(function(num, cb){ + putter({ + type: "test", + time: new Date(Date.UTC(2011, 6, 18, 0, Math.sqrt(num) - 10)).toISOString(), + data: {i: num} + }, function(){ cb(null, null); }); + }, i); } - - setTimeout(waitForEvents,10); + // continue when queue clears + put_queue.await(function(){ callback(null, getter) }); }, + teardown: function(){ this.putter.stop(this.callback); }, + + // FIXME: ---- remove below ------------------------------------ + + "constant expression": metricTest({ expression: "1", start: "2011-07-17T23:47:00.000Z", stop: "2011-07-18T00:00:00.000Z"}, { 60e3: [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] }), + + "unary expression a": metricTest({ + expression: "sum(test)", + start: "2011-07-17T23:47:00.000Z", + stop: "2011-07-18T00:02:00.000Z" + }, { + 60e3: [ 0, 0, 0, 1, 1, 3, 5, 7, 9, 11, 13, 15, 17, 39, 23 ] + }), + + "unary expression b": metricTest({ expression: "sum(test)", start: "2011-07-17T23:47:00.000Z", stop: "2011-07-18T00:00:00.000Z"}, { 60e3: [ 0, 0, 0, 1, 1, 3, 5, 7, 9, 11, 13, 15, 17 ] }), + "unary expression c": metricTest({ expression: "sum(test)", start: "2011-07-17T23:48:00.000Z", stop: "2011-07-18T00:01:00.000Z"}, { 60e3: [ 0, 0, 1, 1, 3, 5, 7, 9, 11, 13, 15, 17, 39 ] }), + "unary expression d": metricTest({ expression: "sum(test)", start: "2011-07-17T23:49:00.000Z", stop: "2011-07-18T00:02:00.000Z"}, { 60e3: [ 0, 1, 1, 3, 5, 7, 9, 11, 13, 15, 17, 39, 23 ] }), + "unary expression e": metricTest({ expression: "sum(test)", start: "2011-07-17T23:50:00.000Z", stop: "2011-07-18T00:03:00.000Z"}, { 60e3: [ 1, 1, 3, 5, 7, 9, 11, 13, 15, 17, 39, 23, 25 ] }), + + "unary expression f": metricTest({ + expression: "sum(test)", + start: "2011-07-17T23:57:00.000Z", + stop: "2011-07-18T00:50:00.000Z" + }, { + 60e3: [13, 15, 17, 39, 23, 25, 27, 29, 31, 33, + 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, + 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, + 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, + 95, 97, 99, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0] + }), + + // FIXME: ---- remove above ------------------------------------ "unary expression": metricTest({ - expression: "sum(test)", - start: "2011-07-17T23:47:00.000Z", - stop: "2011-07-18T00:50:00.000Z", - }, { - 6e4: [0, 0, 0, 1, 1, 3, 5, 7, 9, 11, 13, 15, 17, 39, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - 3e5: [0, 17, 65, 143, 175, 225, 275, 325, 375, 425, 475, 0, 0], - 36e5: [82, 2418], - 864e5: [82, 2418] - } - ), + expression: "sum(test)", + start: "2011-07-17T23:47:00.000Z", + stop: "2011-07-18T00:50:00.000Z" + }, { + 60e3: [0, 0, 0, 1, 1, 3, 5, 7, 9, 11, 13, 15, 17, 39, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 300e3: [0, 17, 65, 143, 175, 225, 275, 325, 375, 425, 475, 0, 0], + 3600e3: [82, 2418], + 86400e3: [82, 2418] + }), "unary expression with data accessor": metricTest({ - expression: "sum(test(i))", - start: "2011-07-17T23:47:00.000Z", - stop: "2011-07-18T00:50:00.000Z" - }, { - 3e5: [0, 136, 3185, 21879, 54600, 115200, 209550, 345150, 529500, 770100, 1074450, 0, 0], - 36e5: [3321, 3120429], - 864e5: [3321, 3120429] - } - ), + expression: "sum(test(i))", + start: "2011-07-17T23:47:00.000Z", + stop: "2011-07-18T00:50:00.000Z" + }, { + 300e3: [0, 136, 3185, 21879, 54600, 115200, 209550, 345150, 529500, 770100, 1074450, 0, 0], + 3600e3: [3321, 3120429], + 86400e3: [3321, 3120429] + }), "unary expression with compound data accessor": metricTest({ - expression: "sum(test(i / 100))", - start: "2011-07-17T23:47:00.000Z", - stop: "2011-07-18T00:50:00.000Z" - }, { - 3e5: [0, 1.36, 31.85, 218.79, 546, 1152, 2095.5, 3451.5, 5295, 7701, 10744.5, 0, 0], - 36e5: [33.21, 31204.29], - 864e5: [33.21, 31204.29] - } - ), + expression: "sum(test(i / 100))", + start: "2011-07-17T23:47:00.000Z", + stop: "2011-07-18T00:50:00.000Z" + }, { + 300e3: [0, 1.36, 31.85, 218.79, 546, 1152, 2095.5, 3451.5, 5295, 7701, 10744.5, 0, 0], + 3600e3: [33.21, 31204.29], + 86400e3: [33.21, 31204.29] + }), "max expression": metricTest({ expression: "max(test(i))", @@ -92,145 +158,195 @@ suite.addBatch(test.batch({ ), "min expression": metricTest({ - expression: "min(test(i))", - start: "2011-07-17T23:47:00.000Z", - stop: "2011-07-18T00:50:00.000Z", - }, { - 36e5: [0, 82], - 864e5: [0, 82] - } - ), + expression: "min(test(i))", + start: "2011-07-17T23:47:00.000Z", + stop: "2011-07-18T00:50:00.000Z", + }, { + 36e5: [0, 82], + 864e5: [0, 82] + }), - "compound expression": metricTest({ - expression: "max(test(i)) - min(test(i))", - start: "2011-07-17T23:47:00.000Z", - stop: "2011-07-18T00:50:00.000Z", - }, { - 3e5: [NaN, 16, 64, 142, 174, 224, 274, 324, 374, 424, 474, NaN, NaN], - 36e5: [81, 2417], - 864e5: [81, 2417] - } - ), + "compound expression (sometimes fails due to race condition?)": metricTest({ + expression: "max(test(i)) - min(test(i))", + start: "2011-07-17T23:47:00.000Z", + stop: "2011-07-18T00:50:00.000Z" + }, { + 300e3: [NaN, 16, 64, 142, 174, 224, 274, 324, 374, 424, 474, NaN, NaN], + 3600e3: [81, 2417], + 86400e3: [81, 2417] + }), "non-pyramidal expression": metricTest({ - expression: "distinct(test(i))", - start: "2011-07-17T23:47:00.000Z", - stop: "2011-07-18T00:50:00.000Z", - }, { - 3e5: [0, 17, 65, 143, 175, 225, 275, 325, 375, 425, 475, 0, 0], - 36e5: [82, 2418], - 864e5: [82, 2418] - } - ), + expression: "distinct(test(i))", + start: "2011-07-17T23:47:00.000Z", + stop: "2011-07-18T00:50:00.000Z" + }, { + 300e3: [0, 17, 65, 143, 175, 225, 275, 325, 375, 425, 475, 0, 0], + 3600e3: [82, 2418], + 86400e3: [82, 2418] + }), "compound pyramidal and non-pyramidal expression": metricTest({ - expression: "sum(test(i)) - median(test(i))", - start: "2011-07-17T23:47:00.000Z", - stop: "2011-07-18T00:50:00.000Z", - }, { - 3e5: [NaN, 128, 3136, 21726, 54288, 114688, 208788, 344088, 528088, 768288, 1072188, NaN, NaN], - 36e5: [3280.5, 3119138.5], - 864e5: [3280.5, 3119138.5] - } - ), + expression: "sum(test(i)) - median(test(i))", + start: "2011-07-17T23:47:00.000Z", + stop: "2011-07-18T00:50:00.000Z" + }, { + 300e3: [NaN, 128, 3136, 21726, 54288, 114688, 208788, 344088, 528088, 768288, 1072188, NaN, NaN], + 3600e3: [3280.5, 3119138.5], + 86400e3: [3280.5, 3119138.5] + }), "compound with constant expression": metricTest({ - expression: "-1 + sum(test)", - start: "2011-07-17T23:47:00.000Z", - stop: "2011-07-18T00:50:00.000Z", - }, { - 3e5: [-1, 16, 64, 142, 174, 224, 274, 324, 374, 424, 474, -1, -1], - 36e5: [81, 2417], - 864e5: [81, 2417] - } - ) + expression: "-1 + sum(test)", + start: "2011-07-17T23:47:00.000Z", + stop: "2011-07-18T00:50:00.000Z" + }, { + 300e3: [-1, 16, 64, 142, 174, 224, 274, 324, 374, 424, 474, -1, -1], + 3600e3: [81, 2417], + 86400e3: [81, 2417] + }) })); -suite.export(module); - +// metricTest -- generates test tree for metrics. +// +// Gets the metric, checks it was calculated correctly from events seeded above; +// then does it again (on a delay) to check that it was cached. +// +// @example given `{ 'unary expression': metricTest({..}, { 60_000: [0, 0, ...], 86_400_000: [82, 2418] })` +// +// { 'unary expression': { +// 'at 1-minute intervals': { +// topic: function get_metrics_with_delay(getter){}, +// 'sum(test)': function metrics_assertions(actual){}, +// '(cached)': { +// topic: function get_metrics_with_delay(err, getter){}, +// 'sum(test)': function metrics_assertions(actual){} } }, +// 'at 1-day intervals': { +// topic: function get_metrics_with_delay(getter){}, +// 'sum(test)': function metrics_assertions(actual){}, +// '(cached)': { +// topic: function get_metrics_with_delay(err, getter){}, +// 'sum(test)': function metrics_assertions(actual){} } } +// } +// } +// function metricTest(request, expected) { - var t = {}, k; - for (k in expected) t["at " + steps[k].description + " intervals"] = testStep(k, expected[k]); - return t; + // { 'at 1-minute intervals': { }, 'at 1-day intervals': { } } + var tree = {}, k; + for (var step in expected) tree["at " + steps[step].description + " intervals"] = testStep(step, expected[step]); + // + // { + // topic: get_metrics_with_delay, + // expression: function(){ + // // rounds down the start time (inclusive) + // // formats UTC time in ISO 8601 + // ... + // // returns the expected values + // }, + // '(cached)': { + // topic: get_metrics_with_delay, + // expression: function(){ + // // rounds down the start time (inclusive) + // ... + // } + // } + // } + // function testStep(step, expected) { - var t = testStepDepth(0, step, expected); - t["(cached)"] = testStepDepth(1, step, expected); - return t; - } - - function testStepDepth(depth, step, expected) { var start = new Date(request.start), - stop = new Date(request.stop); - - var test = { - topic: function() { - var actual = [], - timeout = setTimeout(function() { cb("Time's up!"); }, 10000), - cb = this.callback, - req = Object.create(request), - test = arguments[depth]; - req.step = step; - setTimeout(function() { - test(req, function(response) { - if (response.time >= stop) { - clearTimeout(timeout); - cb(null, actual.sort(function(a, b) { return a.time - b.time; })); - } else { - actual.push(response); - } - }); - }, depth * 250); - } + stop = new Date(request.stop); + + var subtree = { + topic: get_metrics_with_delay(0), + '(cached)': { topic: get_metrics_with_delay(1) } }; + subtree[request.expression] = metrics_assertions(); + subtree["(cached)"][request.expression] = metrics_assertions(); - test[request.expression] = function(actual) { - - // rounds down the start time (inclusive) - var floor = steps[step](start, 0); - assert.deepEqual(actual[0].time, floor); - - // rounds up the stop time (exclusive) - var ceil = steps[step](stop, 0); - if (!(ceil - stop)) ceil = steps[step](stop, -1); - assert.deepEqual(actual[actual.length - 1].time, ceil); - - // formats UTC time in ISO 8601 - actual.forEach(function(d) { - assert.instanceOf(d.time, Date); - assert.match(JSON.stringify(d.time), /[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:00.000Z/); - }); - - // returns exactly one value per time - var i = 0, n = actual.length, t = actual[0].time; - while (++i < n) assert.isTrue(t < (t = actual[i].time)); - - // each metric defines only time and value properties - actual.forEach(function(d) { - assert.deepEqual(Object.keys(d), ["time", "value"]); - }); - - // returns the expected times - var floor = steps[step], - time = floor(start, 0), - times = []; - while (time < stop) { - times.push(time); - time = floor(time, 1); - } - assert.deepEqual(actual.map(function(d) { return d.time; }), times); - - // returns the expected values - var actualValues = actual.map(function(d) { return d.value; }); - assert.equal(expected.length, actual.length, "expected " + expected + ", got " + actualValues); - expected.forEach(function(value, i) { - if (Math.abs(actual[i].value - value) > 1e-6) { - assert.fail(actual.map(function(d) { return d.value; }), expected, "expected {expected}, got {actual} at " + actual[i].time.toISOString()); + function get_metrics_with_delay(depth){ return function(){ + var actual = [], + timeout = setTimeout(function() { console.log(" TIMING OUT NOW", request ); cb(new Error("Time's up!")); }, 40e3), + cb = this.callback, + req = Object.create(request), + getter = arguments[depth]; + req.step = step; + // Wait long enough for the events to have settled in the db. The + // non-cached (depth=0) round can all start in parallel, making this an + // effective `nextTick`. On the secon + setTimeout(function() { + // ... then invoke the metrics getter. As responses roll in, push them + // on to 'actual'; we're done when the 'stop' time is hit + getter(req, function(response){ + if (response.time >= stop) { + clearTimeout(timeout); + cb(null, actual.sort(function(a, b) { return a.time - b.time; })); + } else { + actual.push(response); + } + }); + }, depth * step_testing_delay); + };} + + function metrics_assertions(){ return { + 'rounds down the start time (inclusive)': function(actual) { + var floor = steps[step](start, 0); + assert.deepEqual(actual[0].time, floor); + }, + + 'rounds up the stop time (exclusive)': function(actual){ + var ceil = steps[step](stop, 0); + if (!(ceil - stop)) ceil = steps[step](stop, -1); + assert.deepEqual(actual[actual.length - 1].time, ceil); + }, + + 'formats UTC time in ISO 8601': function(actual){ + actual.forEach(function(d) { + assert.instanceOf(d.time, Date); + assert.match(JSON.stringify(d.time), /[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:00.000Z/); + }); + }, + + 'returns exactly one value per time': function(actual){ + var i = 0, n = actual.length, t = actual[0].time; + while (++i < n) assert.isTrue(t < (t = actual[i].time)); + }, + + 'each metric defines only time and value properties': function(actual){ + actual.forEach(function(d) { + // if ('_trace' in d) delete d._trace; + assert.deepEqual(Object.keys(d), ["time", "value"]); + }); + }, + + 'returns the expected times': function(actual){ + var floor = steps[step], + time = floor(start, 0), + times = []; + while (time < stop) { + times.push(time); + time = floor(time, 1); } - }); + assert.deepEqual(actual.map(function(d) { return d.time; }), times); + }, - }; + 'returns the expected values': function(actual){ + var actualValues = actual.map(function(d) { return d.value; }); + assert.equal(expected.length, actual.length, "expected " + expected + ", got " + actualValues); + expected.forEach(function(value, i) { + if (Math.abs(actual[i].value - value) > 1e-6) { + assert.fail(actual.map(function(d) { return d.value; }), expected, "expected {expected}, got {actual} at " + actual[i].time.toISOString()); + } + }); + } + };} // metric assertions - return test; - } + return subtree; + } // subtree + return tree; +} // tree } +skip(); + +suite['export'](module); + diff --git a/test/reduces-test.js b/test/reduces-test.js index 3fa0c62b..1955aa18 100644 --- a/test/reduces-test.js +++ b/test/reduces-test.js @@ -1,6 +1,8 @@ -var vows = require("vows"), - assert = require("assert"), - reduces = require("../lib/cube/reduces"); +'use strict'; + +var vows = require("vows"), + assert = require("assert"), + reduces = require("../lib/cube/reduces"); var suite = vows.describe("reduces"); @@ -135,4 +137,4 @@ suite.addBatch({ } }); -suite.export(module); +suite['export'](module); diff --git a/test/server-test.js b/test/server-test.js new file mode 100644 index 00000000..84b7fd20 --- /dev/null +++ b/test/server-test.js @@ -0,0 +1,103 @@ +'use strict'; + +var vows = require("vows"), + assert = require("assert"), + test_helper = require("./test_helper"), + cube = require("../"), + endpoint = cube.endpoint, + collector = cube.collector, + metalog = cube.metalog; + +var suite = vows.describe("server"); + +var bucket = { udped: [], httped: [], websocked: [] }; +var now_ish = Date.now(); +var example = { + for_http: { type: "monotreme", time: now_ish, data: { echidnae: 4 } }, + for_udp: { type: "monotreme", time: now_ish, data: { platypodes: 9 } } }; + +function dummy_server(db, endpoints){ + endpoints.udp = function(req, cb){ + // metalog.info('rcvd_udp', { req: req }); + bucket.udped.push(req); + }; + endpoints.http.push( + endpoint('POST', '/1.0/test', collector._post(function(req, cb){ + metalog.info('rcvd_http', { req: req }); + bucket.httped.push(req); + }))); +} + +suite.addBatch( + // Set to `collector` because it's the only one with both http and udp + test_helper.with_server('collector', dummy_server, { + + http: { + topic: test_helper.request({path: "/1.0/test", method: "POST"}, JSON.stringify([example.for_http])), + '': { + topic: test_helper.delaying_topic, + 'sends data to registered putter': function(response){ + assert.deepEqual(bucket.httped.pop(), example.for_http); + assert.isEmpty(bucket.httped); + } + } + }, + + udp: { + topic: test_helper.udp_request(example.for_udp), + '': function(){ + assert.deepEqual(bucket.udped.pop(), example.for_udp); + assert.isEmpty(bucket.udped); + } + }, + + file: { + "GET": { + topic: test_helper.request({path: "/semicolon.js", method: "GET"}), + "the status should be 200": function(response) { + assert.equal(response.statusCode, 200); + }, + "the expected headers should be set": function(response) { + assert.equal(response.headers["content-type"], "application/javascript"); + assert.equal(response.headers["content-length"], 1); + assert.ok(new Date(response.headers["date"]) > Date.UTC(2011, 0, 1)); + assert.ok(new Date(response.headers["last-modified"]) > Date.UTC(2011, 0, 1)); + }, + "the expected content should be returned": function(response) { + assert.equal(response.body, ";"); + } + }, + "GET If-Modified-Since": { + topic: test_helper.request({path: "/semicolon.js", method: "GET", headers: {"if-modified-since": new Date(2101, 0, 1).toUTCString()}}), + "the status should be 304": function(response) { + assert.equal(response.statusCode, 304); + }, + "the expected headers should be set": function(response) { + assert.ok(!("Content-Length" in response.headers)); + assert.ok(new Date(response.headers["date"]) > Date.UTC(2011, 0, 1)); + assert.ok(!("last-modified" in response.headers)); + }, + "no content should be returned": function(response) { + assert.equal(response.body, ""); + } + }, + "HEAD": { + topic: test_helper.request({path: "/semicolon.js", method: "HEAD", headers: {"if-modified-since": new Date(2001, 0, 1).toUTCString()}}), + "the status should be 200": function(response) { + assert.equal(response.statusCode, 200); + }, + "the expected headers should be set": function(response) { + assert.equal(response.headers["content-type"], "application/javascript"); + assert.ok(!("Content-Length" in response.headers)); + assert.ok(new Date(response.headers["date"]) > Date.UTC(2011, 0, 1)); + assert.ok(new Date(response.headers["last-modified"]) > Date.UTC(2011, 0, 1)); + }, + "no content should be returned": function(response) { + assert.equal(response.body, ""); + } + } + } + +})); + +suite['export'](module); diff --git a/test/test_helper.js b/test/test_helper.js new file mode 100644 index 00000000..dc3720f7 --- /dev/null +++ b/test/test_helper.js @@ -0,0 +1,274 @@ +'use strict'; + +var _ = require("underscore"), + util = require("util"), + assert = require("assert"), + http = require("http"), + dgram = require('dgram'), + Db = require("../lib/cube/db"), + metalog = require("../lib/cube/metalog"), + config = require("../lib/cube/config"); + +// ========================================================================== +// +// setup +// + +var test_helper = {}; +var test_collections = ["test_users", "test_events", "test_metrics", "test_boards"]; +test_helper.inspectify = metalog.inspectify; +test_helper._ = require('underscore'); + +config.load({ + mongodb: { + 'mongo-host': 'localhost', + 'mongo-port': 27017, + 'mongo-username': null, + 'mongo-password': null, + 'mongo-database': 'cube_test', + 'host': 'localhost', + 'authentication-collection': 'test_users' + }, + horizons: { + calculation: +(new Date()), + invalidation: +(new Date()) + }, + warmer: { + 'warmer-interval': 10000, + 'warmer-tier': 10000 + } +}); + +var basePort = 1083; +var kinds = { + collector: { + 'http-port': basePort++, + 'udp-port': basePort++, + 'authenticator': 'allow_all' + }, + evaluator: { + 'http-port': basePort++, + 'authenticator': 'allow_all' + } +}; + +// Disable logging for tests. +metalog.loggers.info = metalog.silent; // log +metalog.loggers.minor = metalog.silent; // log +metalog.send_events = false; + +// ========================================================================== +// +// client / server helpers +// + +// test_helper.request -- make an HTTP request. +// +// @param options standard http client options, with these defaults: +// @option host host to contact; default "localhost" +// @option port port to contact; detault `this.http_port` +// @param data request body +// +test_helper.request = function(options, data) { + return function() { + var cb = this.callback; + + options.host = "localhost"; + if (! options.port){ options.port = this.http_port; } + + var request = http.request(options, function(response) { + response.body = ""; + response.setEncoding("utf8"); + response.on("data", function(chunk) { response.body += chunk; }); + response.on("end", function() { cb(null, response); }); + }); + + request.on("error", function(e) { cb(e, null); }); + + if (data && data.length > 0) request.write(data); + request.end(); + }; +}; + +// send udp packet, twiddle thumbs briefly, resume tests. +test_helper.udp_request = function (data){ + return function(){ + var udp_client = dgram.createSocket('udp4'); + var buffer = new Buffer(JSON.stringify(data)); + var ctxt = this, cb = ctxt.callback; + metalog.info('test_sending_udp', { data: data }); + udp_client.send(buffer, 0, buffer.length, ctxt.udp_port, 'localhost', + function(err, val){ delay(cb, ctxt)(err, val); udp_client.close(); } ); + }; +}; + +// proxies to the test context's callback after a short delay. +// +// @example the test topic introduces a delay; the 'is party time' vow gets the same data the cb otherwise would have: +// { topic: send_some_data, +// 'a short time later': { +// topic: test_helper.delaying_topic, +// 'is party time': function(arg){ assert.isAwesome(...) } } } +// +function delaying_topic(){ + var args = Array.prototype.slice.apply(arguments); + args.unshift(null); + delay(this.callback, this).apply(this, args); +} +test_helper.delaying_topic = delaying_topic; + +// returns a callback that once triggered, delays briefly, then passes the same +// args to the actual context's callback +// +// @example +// // you +// dcb = delay(this) +// foo.do_something('...', dcb); +// // foo, after do_something'ing, invokes the delayed callback +// dcb(null, 1, 2); +// // 50ms later, dcb does the equivalent of +// this.callback(null, 1, 2); +// +function delay(orig_cb, ctxt, ms){ + ctxt = ctxt || null; + ms = ms || 100; + return function(){ + var args = arguments; + setTimeout(function(){ orig_cb.apply(ctxt, args); }, ms); + }; +} +test_helper.delay = delay; + +// test_helper.with_server -- +// start server, run tests once server starts, stop server when tests are done +// +// inscribes 'server', 'udp_port' and 'http_port' on the test context -- letting +// you say 'this.server' in your topics, etc. +// +// @param kind -- types of server to run. +// @param components -- passed to server.register() +// @param batch -- the tests to run +test_helper.with_server = function(kind, components, batch){ + return test_helper.batch({ '': { + topic: function(test_db){ + var ctxt = this; + start_server(kind, components, ctxt, test_db); + }, + '': batch, + teardown: function(j_, test_db){ + var callback = this.callback; + this.server.stop(function(){ + metalog.info('test_server_batch_closed'); + callback(); + }); + } + } }); +}; + +// @see test_helper.with_server +function start_server(kind, register, ctxt, test_db){ + var config = require('../lib/cube/config').load(kinds[kind]); + ctxt.http_port = config['http-port']; + ctxt.udp_port = config['udp-port']; + ctxt.server = require('../lib/cube/server')(config, test_db); + ctxt.server.use(register); + ctxt.server.start(ctxt.callback); +} + +// ========================================================================== +// +// db helpers +// + +// test_helper.batch -- +// * connect to db, drop relevant collections +// * run tests once db is ready; +// * close db when tests are done +test_helper.batch = function(batch) { + metalog.info('batch', batch); + return { + "": { + topic: function() { + var ctxt = this; + ctxt.db = new Db(); + ctxt.db.open(function(error){ + drop_and_reopen_collections(ctxt.db, function(error){ + ctxt.callback.apply(ctxt, arguments); + ctxt.db.clearCache(); + }); + }); + }, + "": batch, + teardown: function(){ + var callback = this.callback; + this.db.close(function(){ + metalog.info('test_db_batch_closed'); + callback(); + }); + } + } + }; +}; + +// test_db.using_objects -- scaffold fixtures into the database, run tests once loaded. +// +// Wrap your tests in test_helper.batch to get the test_db object. +Db.prototype.using_objects = function (clxn_name, test_objects, ctxt){ + var test_db = this; + metalog.minor('test_db_loading_objects', test_objects); + test_db.collection(clxn_name, function(err, clxn){ + if (err) throw(err); + ctxt[clxn_name] = clxn; + clxn.remove({ dummy: true }, function(){ + clxn.insert(test_objects, function(){ + ctxt.callback(null, test_db); + }); }); + }); +}; + +// ========================================================================== +// +// db methods +// + +// @see test_helper.batch +function drop_and_reopen_collections(test_db, cb){ + metalog.minor('test_db_drop_collections', { collections: test_collections }); + + var collectionsRemaining = test_collections.length; + test_collections.forEach(function(collection_name){ + test_db.collection(collection_name, function(error, collection){ + collection.drop(collectionReady); + }) + }); + function collectionReady() { + if (!--collectionsRemaining) { + cb(null, test_db); + } + } +} + +// ========================================================================== +// +// assertions +// + +assert.isCalledTimes = function(ctxt, reps){ + var results = [], finished = false; + setTimeout(function(){ if (! finished){ ctxt.callback(new Error('timeout: need '+reps+' results only have '+util.inspect(results))); } }, 2000); + return function _is_called_checker(){ + results.push(_.toArray(arguments)); + if (results.length >= reps){ finished = true; ctxt.callback(null, results); } + }; +}; + +assert.isNotCalled = function(name){ + return function(){ throw new Error(name + ' should not have been called, but was'); }; +}; + +// ========================================================================== +// +// fin. +// + +module.exports = test_helper; diff --git a/test/tiers-test.js b/test/tiers-test.js index a638b260..38c6b10f 100644 --- a/test/tiers-test.js +++ b/test/tiers-test.js @@ -1,6 +1,8 @@ -var vows = require("vows"), - assert = require("assert"), - tiers = require("../lib/cube/tiers"); +'use strict'; + +var vows = require("vows"), + assert = require("assert"), + tiers = require("../lib/cube/tiers"); var suite = vows.describe("tiers"); @@ -31,51 +33,51 @@ suite.addBatch({ "floor": { "rounds down to 10-seconds": function(tier) { - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 00, 20)), utc(2011, 08, 02, 12, 00, 20)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 00, 21)), utc(2011, 08, 02, 12, 00, 20)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 00, 23)), utc(2011, 08, 02, 12, 00, 20)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 00, 39)), utc(2011, 08, 02, 12, 00, 30)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 00, 40)), utc(2011, 08, 02, 12, 00, 40)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 0, 20)), utc(2011, 8, 2, 12, 0, 20)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 0, 21)), utc(2011, 8, 2, 12, 0, 20)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 0, 23)), utc(2011, 8, 2, 12, 0, 20)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 0, 39)), utc(2011, 8, 2, 12, 0, 30)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 0, 40)), utc(2011, 8, 2, 12, 0, 40)); }, "does not modify the passed-in date": function(tier) { - var date = utc(2011, 08, 02, 12, 00, 21); - assert.deepEqual(tier.floor(date), utc(2011, 08, 02, 12, 00, 20)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 00, 21)); + var date = utc(2011, 8, 2, 12, 0, 21); + assert.deepEqual(tier.floor(date), utc(2011, 8, 2, 12, 0, 20)); + assert.deepEqual(date, utc(2011, 8, 2, 12, 0, 21)); } }, "ceil": { "rounds up to 10-seconds": function(tier) { - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 00, 20)), utc(2011, 08, 02, 12, 00, 20)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 00, 21)), utc(2011, 08, 02, 12, 00, 30)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 00, 23)), utc(2011, 08, 02, 12, 00, 30)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 00, 39)), utc(2011, 08, 02, 12, 00, 40)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 00, 40)), utc(2011, 08, 02, 12, 00, 40)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 0, 20)), utc(2011, 8, 2, 12, 0, 20)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 0, 21)), utc(2011, 8, 2, 12, 0, 30)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 0, 23)), utc(2011, 8, 2, 12, 0, 30)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 0, 39)), utc(2011, 8, 2, 12, 0, 40)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 0, 40)), utc(2011, 8, 2, 12, 0, 40)); }, "does not modified the specified date": function(tier) { - var date = utc(2011, 08, 02, 12, 00, 21); - assert.deepEqual(tier.ceil(date), utc(2011, 08, 02, 12, 00, 30)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 00, 21)); + var date = utc(2011, 8, 2, 12, 0, 21); + assert.deepEqual(tier.ceil(date), utc(2011, 8, 2, 12, 0, 30)); + assert.deepEqual(date, utc(2011, 8, 2, 12, 0, 21)); } }, "step": { "increments time by ten seconds": function(tier) { - var date = utc(2011, 08, 02, 23, 59, 20); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 59, 30)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 59, 40)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 59, 50)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 03, 00, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 03, 00, 00, 10)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 03, 00, 00, 20)); + var date = utc(2011, 8, 2, 23, 59, 20); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 2, 23, 59, 30)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 2, 23, 59, 40)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 2, 23, 59, 50)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 3, 0, 0, 0)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 3, 0, 0, 10)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 3, 0, 0, 20)); }, "does not round the specified date": function(tier) { - assert.deepEqual(tier.step(utc(2011, 08, 02, 12, 21, 23)), utc(2011, 08, 02, 12, 21, 33)); + assert.deepEqual(tier.step(utc(2011, 8, 2, 12, 21, 23)), utc(2011, 8, 2, 12, 21, 33)); }, "does not modify the specified date": function(tier) { - var date = utc(2011, 08, 02, 12, 20, 00); - assert.deepEqual(tier.step(date), utc(2011, 08, 02, 12, 20, 10)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 20, 00)); + var date = utc(2011, 8, 2, 12, 20, 0); + assert.deepEqual(tier.step(date), utc(2011, 8, 2, 12, 20, 10)); + assert.deepEqual(date, utc(2011, 8, 2, 12, 20, 0)); } } }, @@ -85,61 +87,60 @@ suite.addBatch({ "has the key 6e4": function(tier) { assert.strictEqual(tier.key, 6e4); }, - "next is undefined": function(tier) { - assert.isUndefined(tier.next); + "next is the 10-second tier": function(tier) { + assert.equal(tier.next, tiers[1e4]); }, - "size is undefined": function(tier) { - assert.isUndefined(tier.size); + "size is 6": function(tier) { + assert.strictEqual(tier.size(), 6); }, - "floor": { "rounds down to minutes": function(tier) { - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 20, 00)), utc(2011, 08, 02, 12, 20)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 20, 01)), utc(2011, 08, 02, 12, 20)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 21, 00)), utc(2011, 08, 02, 12, 21)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 23, 00)), utc(2011, 08, 02, 12, 23)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 24, 59)), utc(2011, 08, 02, 12, 24)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 25, 00)), utc(2011, 08, 02, 12, 25)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 20, 0)), utc(2011, 8, 2, 12, 20)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 20, 1)), utc(2011, 8, 2, 12, 20)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 21, 0)), utc(2011, 8, 2, 12, 21)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 23, 0)), utc(2011, 8, 2, 12, 23)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 24, 59)), utc(2011, 8, 2, 12, 24)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 25, 0)), utc(2011, 8, 2, 12, 25)); }, "does not modify the passed-in date": function(tier) { - var date = utc(2011, 08, 02, 12, 21, 20); - assert.deepEqual(tier.floor(date), utc(2011, 08, 02, 12, 21)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 21, 20)); + var date = utc(2011, 8, 2, 12, 21, 20); + assert.deepEqual(tier.floor(date), utc(2011, 8, 2, 12, 21)); + assert.deepEqual(date, utc(2011, 8, 2, 12, 21, 20)); } }, "ceil": { "rounds up to minutes": function(tier) { - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 20, 00)), utc(2011, 08, 02, 12, 20)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 20, 01)), utc(2011, 08, 02, 12, 21)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 21, 00)), utc(2011, 08, 02, 12, 21)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 23, 00)), utc(2011, 08, 02, 12, 23)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 24, 59)), utc(2011, 08, 02, 12, 25)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 25, 00)), utc(2011, 08, 02, 12, 25)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 20, 0)), utc(2011, 8, 2, 12, 20)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 20, 1)), utc(2011, 8, 2, 12, 21)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 21, 0)), utc(2011, 8, 2, 12, 21)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 23, 0)), utc(2011, 8, 2, 12, 23)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 24, 59)), utc(2011, 8, 2, 12, 25)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 25, 0)), utc(2011, 8, 2, 12, 25)); }, "does not modified the specified date": function(tier) { - var date = utc(2011, 08, 02, 12, 21, 20); - assert.deepEqual(tier.ceil(date), utc(2011, 08, 02, 12, 22)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 21, 20)); + var date = utc(2011, 8, 2, 12, 21, 20); + assert.deepEqual(tier.ceil(date), utc(2011, 8, 2, 12, 22)); + assert.deepEqual(date, utc(2011, 8, 2, 12, 21, 20)); } }, "step": { "increments time by one minute": function(tier) { - var date = utc(2011, 08, 02, 23, 45, 00); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 46)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 47)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 48)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 49)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 50)); + var date = utc(2011, 8, 2, 23, 45, 0); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 2, 23, 46)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 2, 23, 47)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 2, 23, 48)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 2, 23, 49)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 2, 23, 50)); }, "does not round the specified date": function(tier) { - assert.deepEqual(tier.step(utc(2011, 08, 02, 12, 21, 23)), utc(2011, 08, 02, 12, 22, 23)); + assert.deepEqual(tier.step(utc(2011, 8, 2, 12, 21, 23)), utc(2011, 8, 2, 12, 22, 23)); }, "does not modify the specified date": function(tier) { - var date = utc(2011, 08, 02, 12, 20); - assert.deepEqual(tier.step(date), utc(2011, 08, 02, 12, 21)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 20)); + var date = utc(2011, 8, 2, 12, 20); + assert.deepEqual(tier.step(date), utc(2011, 8, 2, 12, 21)); + assert.deepEqual(date, utc(2011, 8, 2, 12, 20)); } } }, @@ -149,61 +150,61 @@ suite.addBatch({ "has the key 3e5": function(tier) { assert.strictEqual(tier.key, 3e5); }, - "next is undefined": function(tier) { - assert.isUndefined(tier.next); + "next is the minute tier": function(tier) { + assert.equal(tier.next, tiers[6e4]); }, - "size is undefined": function(tier) { - assert.isUndefined(tier.size); + "size is 5": function(tier) { + assert.strictEqual(tier.size(), 5); }, "floor": { "rounds down to 5-minutes": function(tier) { - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 20, 00)), utc(2011, 08, 02, 12, 20)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 20, 01)), utc(2011, 08, 02, 12, 20)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 21, 00)), utc(2011, 08, 02, 12, 20)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 23, 00)), utc(2011, 08, 02, 12, 20)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 24, 59)), utc(2011, 08, 02, 12, 20)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 25, 00)), utc(2011, 08, 02, 12, 25)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 20, 0)), utc(2011, 8, 2, 12, 20)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 20, 1)), utc(2011, 8, 2, 12, 20)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 21, 0)), utc(2011, 8, 2, 12, 20)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 23, 0)), utc(2011, 8, 2, 12, 20)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 24, 59)), utc(2011, 8, 2, 12, 20)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 25, 0)), utc(2011, 8, 2, 12, 25)); }, "does not modify the passed-in date": function(tier) { - var date = utc(2011, 08, 02, 12, 21); - assert.deepEqual(tier.floor(date), utc(2011, 08, 02, 12, 20)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 21)); + var date = utc(2011, 8, 2, 12, 21); + assert.deepEqual(tier.floor(date), utc(2011, 8, 2, 12, 20)); + assert.deepEqual(date, utc(2011, 8, 2, 12, 21)); } }, "ceil": { "rounds up to 5-minutes": function(tier) { - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 20, 00)), utc(2011, 08, 02, 12, 20)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 20, 01)), utc(2011, 08, 02, 12, 25)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 21, 00)), utc(2011, 08, 02, 12, 25)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 23, 00)), utc(2011, 08, 02, 12, 25)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 24, 59)), utc(2011, 08, 02, 12, 25)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 25, 00)), utc(2011, 08, 02, 12, 25)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 20, 0)), utc(2011, 8, 2, 12, 20)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 20, 1)), utc(2011, 8, 2, 12, 25)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 21, 0)), utc(2011, 8, 2, 12, 25)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 23, 0)), utc(2011, 8, 2, 12, 25)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 24, 59)), utc(2011, 8, 2, 12, 25)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 25, 0)), utc(2011, 8, 2, 12, 25)); }, "does not modified the specified date": function(tier) { - var date = utc(2011, 08, 02, 12, 21, 00); - assert.deepEqual(tier.ceil(date), utc(2011, 08, 02, 12, 25)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 21)); + var date = utc(2011, 8, 2, 12, 21, 0); + assert.deepEqual(tier.ceil(date), utc(2011, 8, 2, 12, 25)); + assert.deepEqual(date, utc(2011, 8, 2, 12, 21)); } }, "step": { "increments time by five minutes": function(tier) { - var date = utc(2011, 08, 02, 23, 45, 00); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 50)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 55)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 03, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 03, 00, 05)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 03, 00, 10)); + var date = utc(2011, 8, 2, 23, 45, 0); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 2, 23, 50)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 2, 23, 55)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 3, 0, 0)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 3, 0, 5)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 3, 0, 10)); }, "does not round the specified date": function(tier) { - assert.deepEqual(tier.step(utc(2011, 08, 02, 12, 21, 23)), utc(2011, 08, 02, 12, 26, 23)); + assert.deepEqual(tier.step(utc(2011, 8, 2, 12, 21, 23)), utc(2011, 8, 2, 12, 26, 23)); }, "does not modify the specified date": function(tier) { - var date = utc(2011, 08, 02, 12, 20, 00); - assert.deepEqual(tier.step(date), utc(2011, 08, 02, 12, 25)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 20)); + var date = utc(2011, 8, 2, 12, 20, 0); + assert.deepEqual(tier.step(date), utc(2011, 8, 2, 12, 25)); + assert.deepEqual(date, utc(2011, 8, 2, 12, 20)); } } }, @@ -222,50 +223,50 @@ suite.addBatch({ "floor": { "rounds down to hours": function(tier) { - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 00, 00)), utc(2011, 08, 02, 12, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 00, 01)), utc(2011, 08, 02, 12, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 21, 00)), utc(2011, 08, 02, 12, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 59, 59)), utc(2011, 08, 02, 12, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 13, 00, 00)), utc(2011, 08, 02, 13, 00)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 0, 0)), utc(2011, 8, 2, 12, 0)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 0, 1)), utc(2011, 8, 2, 12, 0)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 21, 0)), utc(2011, 8, 2, 12, 0)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 59, 59)), utc(2011, 8, 2, 12, 0)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 13, 0, 0)), utc(2011, 8, 2, 13, 0)); }, "does not modify the passed-in date": function(tier) { - var date = utc(2011, 08, 02, 12, 21); - assert.deepEqual(tier.floor(date), utc(2011, 08, 02, 12, 00)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 21)); + var date = utc(2011, 8, 2, 12, 21); + assert.deepEqual(tier.floor(date), utc(2011, 8, 2, 12, 0)); + assert.deepEqual(date, utc(2011, 8, 2, 12, 21)); } }, "ceil": { "rounds up to hours": function(tier) { - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 00, 00)), utc(2011, 08, 02, 12, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 00, 01)), utc(2011, 08, 02, 13, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 21, 00)), utc(2011, 08, 02, 13, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 59, 59)), utc(2011, 08, 02, 13, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 13, 00, 00)), utc(2011, 08, 02, 13, 00)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 0, 0)), utc(2011, 8, 2, 12, 0)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 0, 1)), utc(2011, 8, 2, 13, 0)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 21, 0)), utc(2011, 8, 2, 13, 0)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 59, 59)), utc(2011, 8, 2, 13, 0)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 13, 0, 0)), utc(2011, 8, 2, 13, 0)); }, "does not modified the specified date": function(tier) { - var date = utc(2011, 08, 02, 12, 21, 00); - assert.deepEqual(tier.ceil(date), utc(2011, 08, 02, 13, 00)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 21)); + var date = utc(2011, 8, 2, 12, 21, 0); + assert.deepEqual(tier.ceil(date), utc(2011, 8, 2, 13, 0)); + assert.deepEqual(date, utc(2011, 8, 2, 12, 21)); } }, "step": { "increments time by one hour": function(tier) { - var date = utc(2011, 08, 02, 22, 00, 00); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 03, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 03, 01, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 03, 02, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 03, 03, 00)); + var date = utc(2011, 8, 2, 22, 0, 0); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 2, 23, 0)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 3, 0, 0)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 3, 1, 0)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 3, 2, 0)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 3, 3, 0)); }, "does not round the specified date": function(tier) { - assert.deepEqual(tier.step(utc(2011, 08, 02, 12, 21, 23)), utc(2011, 08, 02, 13, 21, 23)); + assert.deepEqual(tier.step(utc(2011, 8, 2, 12, 21, 23)), utc(2011, 8, 2, 13, 21, 23)); }, "does not modify the specified date": function(tier) { - var date = utc(2011, 08, 02, 12, 00, 00); - assert.deepEqual(tier.step(date), utc(2011, 08, 02, 13, 00)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 00)); + var date = utc(2011, 8, 2, 12, 0, 0); + assert.deepEqual(tier.step(date), utc(2011, 8, 2, 13, 0)); + assert.deepEqual(date, utc(2011, 8, 2, 12, 0)); } } }, @@ -284,50 +285,50 @@ suite.addBatch({ "floor": { "rounds down to days": function(tier) { - assert.deepEqual(tier.floor(utc(2011, 08, 02, 00, 00, 00)), utc(2011, 08, 02, 00, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 00, 00, 01)), utc(2011, 08, 02, 00, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 21, 00)), utc(2011, 08, 02, 00, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 02, 23, 59, 59)), utc(2011, 08, 02, 00, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 03, 00, 00, 00)), utc(2011, 08, 03, 00, 00)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 0, 0, 0)), utc(2011, 8, 2, 0, 0)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 0, 0, 1)), utc(2011, 8, 2, 0, 0)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 12, 21, 0)), utc(2011, 8, 2, 0, 0)); + assert.deepEqual(tier.floor(utc(2011, 8, 2, 23, 59, 59)), utc(2011, 8, 2, 0, 0)); + assert.deepEqual(tier.floor(utc(2011, 8, 3, 0, 0, 0)), utc(2011, 8, 3, 0, 0)); }, "does not modify the passed-in date": function(tier) { - var date = utc(2011, 08, 02, 12, 21); - assert.deepEqual(tier.floor(date), utc(2011, 08, 02, 00, 00)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 21)); + var date = utc(2011, 8, 2, 12, 21); + assert.deepEqual(tier.floor(date), utc(2011, 8, 2, 0, 0)); + assert.deepEqual(date, utc(2011, 8, 2, 12, 21)); } }, "ceil": { "rounds up to days": function(tier) { - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 00, 00, 00)), utc(2011, 08, 02, 00, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 00, 00, 01)), utc(2011, 08, 03, 00, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 21, 00)), utc(2011, 08, 03, 00, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 02, 23, 59, 59)), utc(2011, 08, 03, 00, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 03, 00, 00, 00)), utc(2011, 08, 03, 00, 00)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 0, 0, 0)), utc(2011, 8, 2, 0, 0)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 0, 0, 1)), utc(2011, 8, 3, 0, 0)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 12, 21, 0)), utc(2011, 8, 3, 0, 0)); + assert.deepEqual(tier.ceil(utc(2011, 8, 2, 23, 59, 59)), utc(2011, 8, 3, 0, 0)); + assert.deepEqual(tier.ceil(utc(2011, 8, 3, 0, 0, 0)), utc(2011, 8, 3, 0, 0)); }, "does not modified the specified date": function(tier) { - var date = utc(2011, 08, 02, 12, 21, 00); - assert.deepEqual(tier.ceil(date), utc(2011, 08, 03, 00, 00)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 21)); + var date = utc(2011, 8, 2, 12, 21, 0); + assert.deepEqual(tier.ceil(date), utc(2011, 8, 3, 0, 0)); + assert.deepEqual(date, utc(2011, 8, 2, 12, 21)); } }, "step": { "increments time by one day": function(tier) { - var date = utc(2011, 08, 02, 00, 00, 00); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 03, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 04, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 05, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 06, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 07, 00, 00)); + var date = utc(2011, 8, 2, 0, 0, 0); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 3, 0, 0)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 4, 0, 0)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 5, 0, 0)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 6, 0, 0)); + assert.deepEqual(date = tier.step(date), utc(2011, 8, 7, 0, 0)); }, "does not round the specified date": function(tier) { - assert.deepEqual(tier.step(utc(2011, 08, 02, 12, 21, 23)), utc(2011, 08, 03, 12, 21, 23)); + assert.deepEqual(tier.step(utc(2011, 8, 2, 12, 21, 23)), utc(2011, 8, 3, 12, 21, 23)); }, "does not modify the specified date": function(tier) { - var date = utc(2011, 08, 02, 00, 00, 00); - assert.deepEqual(tier.step(date), utc(2011, 08, 03, 00, 00)); - assert.deepEqual(date, utc(2011, 08, 02, 00, 00)); + var date = utc(2011, 8, 2, 0, 0, 0); + assert.deepEqual(tier.step(date), utc(2011, 8, 3, 0, 0)); + assert.deepEqual(date, utc(2011, 8, 2, 0, 0)); } } } @@ -338,4 +339,4 @@ function utc(year, month, day, hours, minutes, seconds) { return new Date(Date.UTC(year, month, day, hours || 0, minutes || 0, seconds || 0)); } -suite.export(module); +suite['export'](module); diff --git a/test/types-test.js b/test/types-test.js deleted file mode 100644 index 9aaa6484..00000000 --- a/test/types-test.js +++ /dev/null @@ -1,37 +0,0 @@ -var vows = require("vows"), - assert = require("assert"), - test = require("./helpers"), - types = require("../lib/cube/types"), - mongodb = require("mongodb"); - -var suite = vows.describe("types"); - -suite.addBatch(test.batch({ - topic: function(test) { - return types(test.db); - }, - - "types": { - "returns collection cache for a given database": function(types) { - assert.equal(typeof types, "function"); - }, - "each typed collection has events and metrics": function(types) { - var collection = types("random"), - keys = []; - for (var key in collection) { - keys.push(key); - } - keys.sort(); - assert.deepEqual(keys, ["events", "metrics"]); - assert.isTrue(collection.events instanceof mongodb.Collection); - assert.isTrue(collection.metrics instanceof mongodb.Collection); - assert.equal(collection.events.collectionName, "random_events"); - assert.equal(collection.metrics.collectionName, "random_metrics"); - }, - "memoizes cached collections": function(types) { - assert.strictEqual(types("random"), types("random")); - } - } -})); - -suite.export(module); diff --git a/test/visualizer-test.js b/test/visualizer-test.js new file mode 100644 index 00000000..5167c149 --- /dev/null +++ b/test/visualizer-test.js @@ -0,0 +1,27 @@ +'use strict'; + +var vows = require("vows"), + assert = require("assert"), + cube = require("../"), + test_helper = require("./test_helper"); + +var suite = vows.describe("visualizer"); + +function frontend_components() { + cube.evaluator.register.apply(this, arguments); + cube.visualizer.register.apply(this, arguments); +} + +// suite.addBatch( +// test_helper.with_server('evaluator', frontend_components, { +// +// "POST /event/put with invalid JSON": { +// topic: test_helper.request({method: "POST", path: "/1.0/event/put"}, "This ain't JSON.\n"), +// "responds with status 400": function(response) { +// assert.equal(response.statusCode, 400); +// assert.deepEqual(JSON.parse(response.body), {error: "SyntaxError: Unexpected token T"}); +// } +// } +// })); + +suite['export'](module); diff --git a/test/warmer-test.js b/test/warmer-test.js new file mode 100644 index 00000000..515c70c2 --- /dev/null +++ b/test/warmer-test.js @@ -0,0 +1,60 @@ +'use strict'; + +var _ = require('underscore'), + vows = require("vows"), + assert = require("assert"), + test_helper = require("./test_helper"), + event = require("../lib/cube/event"), + config = require("../lib/cube/config"), + warmer = require("../lib/cube/warmer"); + +var suite = vows.describe("warmer"); + +suite.addBatch(test_helper.batch({ + topic: function(test_db) { + // Temporarily override horizons settings + this.oldHorizons = config.horizons; + config.horizons = { calculation: 30000 }; + + var board = { pieces: [{ query: "sum(test(value))" }] }, + nowish = this.nowish = (10e3 * Math.floor(new Date()/10e3)), + putter = this.putter = event.putter(test_db), + _this = this; + + this.warmer = warmer(config); + + putter({type: 'test', time: nowish + 500, data: {value: 10}}, function(){ + putter({type: 'test', time: nowish + 2000, data: {value: 5}}, function(){ + test_db.using_objects("boards", [board], {callback: function(error){ + if(error) return _this.callback(error); + _this.callback(null, test_db); + }}); + }); + }); + }, + 'calculates': { + topic: function(test_db){ + var _this = this; + + this.warmer.start(); + setTimeout(function(){ + test_db.metrics('test', function(error, collection){ + collection.find().toArray(_this.callback); + }); + }, 1000); + }, + 'correct number of metrics': function(metrics){ assert.equal(metrics.length, 3); }, + 'correct values for metrics': function(metrics){ + assert.equal(metrics[0].v, 0); assert.equal(+metrics[0]._id.t, +this.nowish - 20000); + assert.equal(metrics[1].v, 0); assert.equal(+metrics[1]._id.t, +this.nowish - 10000); + assert.equal(metrics[2].v, 15); assert.equal(+metrics[2]._id.t, +this.nowish); + } + }, + teardown: function(){ + config.horizons = this.oldHorizons; + this.warmer.stop(); + this.putter.stop(this.callback); + } +})); + +suite['export'](module);