The oldest thing here, and the first repository: js-meter, a profiling tool for HTML and JavaScript with particular attention to jQuery — measuring what a page actually does at runtime when hand-written JavaScript starts piling up.
I published it in October 2009, the week I joined GitHub, from a Taktsoft address. Everything after this page is downstream of that instinct: make the invisible measurable.
| Fact | Detail |
|---|---|
| Purpose | JavaScript / jQuery runtime profiling |
| Released | October 2009 |
| Context | Taktsoft years |
| Status | Original release, kept for the record |
What's in it
Published the same week the GitHub account was created — the starting point of everything else on this page.
The same instinct as the later cache warmer, exporter and performance work.
In detail
What is actually inside it
Before package managers for frontend code were the norm, a tool shipped as files in a document root. htdocs/css/images/ui-* is unmodified jQuery UI theme furniture.
Published 8 October 2009 — the week the GitHub account was created. Everything else on this page is downstream of it.
| Detail | Value |
|---|---|
| Purpose | JavaScript / jQuery runtime profiling |
| Distribution | files in a document root (`htdocs/`) |
| Released | October 2009 |
Technical notes
The decisions behind it
What was the problem you were solving in 2009?
Instrumenting jQuery from the inside
238 lines that wrap the library, not the browser
In 2009 the library was the thing to measure, so jquery-profile.js wraps jQuery itself instead of sampling the browser. It is a 238-line IIFE that has to be loaded after jQuery, because the first thing it does is take a snapshot: var handle = jQuery.event.handle, ready = jQuery.ready;. From then on every dispatched DOM event goes through a replacement handler that times it, and the profile is a tree — events, each holding the jQuery calls issued while it ran.
Method capture is a loop over the prototype: for (var method in jQuery.fn) wraps every method except init and stores { name, inLength: this.length, args, duration, outLength: ret.length }. An internal flag is set while a wrapper runs, so jQuery's own nested calls are not counted twice. jQuery.fn.init gets a special case that also records the selector string and the context node — the part that answers which selector was slow. The guard compares ret.selector.substr(0, test.length) != test against the literal source of the wrapper, so the profiler never records its own code as a selector.
Events nest, so the log keeps an eventStack and a curEvent pointer. Code that runs outside any handler — the usual top-level $(...).css(...) on load — lands in a synthetic bucket called inline at log[0]. A jQuery.readyList.unshift(...) entry adds a ready pseudo-event. Events that produced no methods and took ≤1 ms are discarded with log.splice(pos, 1).
The clock is (new Date).getTime() — whole milliseconds — so that drop rule follows directly: a single fast jQuery call often rounded to zero. The tool was never going to rank micro-optimisations; it ranked aggregates and outliers. Every record also carries FunctionMonitor.currentFunction as m.fn, which is how FunMon2's named application functions attach to the jQuery calls that invoked them.
stack_len: 20 ring buffer with hold_max: 1 keeps the slowest of the last 20 calls, not the average.From browser to report
One POST per jQuery call, aggregated in SQL
The client had no backend of its own. displayProfile() walks the captured log and, for every method, issues one $.post(FunctionMonitor.logUrl, { json: JSON.stringify(data), type: "jquery" }) — one row per jQuery call, straight from the browser to PHP. FunctionMonitor.logUrl is set by hand in the host page; in index_logging.html it is /jsprofiler/jsonlogger.php, which is also the only place the intended folder name under the document root appears.
jsonlogger.php is deliberately dumb. It decodes the JSON, uses the type request parameter as the table name, and maps the JSON keys straight onto columns, appending a timestamp. With no type everything lands in a single jsonlog(timestamp, json) table. It decodes with PEAR's Services_JSON in loose mode rather than the native json_decode, and the database path still uses the mysql_* API — mysql_connect(null, 'root', ''). Escaping is a preg_replace("/'/", "\'", ...) sitting next to a comment claiming it prevents SQL injection; the table name taken from $_REQUEST['type'] is concatenated unescaped, which it does not prevent.
report.php turns rows back into runs without any session id: it selects GROUP BY (timestamp) and declares a new profiling run whenever the gap between consecutive timestamps exceeds one second. A run's headline number is SUM(duration) over its rows. Everything else is aggregation in SQL — functions grouped by functionName, queries grouped by event, functionName, name, args, context — with percent() from util.php formatting one decimal, and shorten() clipping selectors to 30 characters (100 for arguments in detail rows) into <abbr> tooltips. Rendering is Smarty plus the jQuery UI accordion; the pie chart is a Google Image Charts URL assembled in the template, http://chart.apis.google.com/chart?cht=p&chd=t:…&chl=…&chs=700x250&chco=224499.
| Table | Columns, in the order jsonlogger.php maps them |
|---|---|
| `jquery` | `event`, `duration`, `functionName`, `methodNumber`, `name`, `args`, `inLength`, `outLength`, `selector`, `context` |
| `funmon` | `functionName`, `callNumber`, `time`, `process`, `trace` |
| Run boundary | not stored — report.php starts a new run when consecutive timestamps are more than 1 s apart |
Shipping it as files
The install was the README
The README splits the tree in two. Into the openly accessible document root go db.php, index_logging.html, jsonlogger.php, report.php, util.php and the javascript/ bundle. Into the Smarty template directory go templates/reports.tpl and single_report.tpl — with the reminder to edit report.php so it can find them.
db.php is four lines of PDO settings — $dbHost, $dbName = 'jquery_log', $dbUser = 'root', $dbPass = '' — but only report.php includes it; the JSON endpoint opens its own mysql_* connection with the credentials hardcoded a second time. Nothing migrates the schema: the two CREATE TABLE statements live in a comment block in jsonlogger.php.
report.php opens with $smarty_dir = "D:/xampp/smarty" and $jsprofiler_docroot = "D:/xampp/htdocs/jsprofiler", then points Smarty's configs, templates, templates_c and cache directories under them. Deployment meant editing those strings — there is no config file.
index_logging.html is XHTML 1.1 and loads firebug-lite-compressed.js (1.2) through an <!--[if IE]> conditional comment, because Firefox already had Firebug. The same page sets FunctionMonitor.logUrl, calls readyMaster() and leaves jQuery.displayProfile() commented out, so the visible panel is opt-in.
reports.tpl links css/jquery-ui-1.7.2.custom.css, which was not committed — only the theme images, while css/profiler/report.css spells the jQuery UI Sunny palette out by hand. readyMaster() is called but defined nowhere in the repository. README~ is a zero-byte editor backup, committed with the import.
The same problem in 2026
What replaced the hook
The question js-meter answered — which selector, which handler, how many elements, how long — is a built-in now. Chrome DevTools' Performance panel records selector statistics: elapsed matching time per selector plus match attempts. performance.now() gives microsecond-scale timestamps where (new Date).getTime() gave integers, and the User Timing API (performance.mark / performance.measure) with PerformanceObserver lets a page publish its own named ranges without monkey-patching a library. Frame attribution moved from a hand-rolled JavaScript eventStack to the browser's own call trees.
The other half of js-meter — instrument in the page, POST JSON, aggregate server-side — survived as standard instrumentation. OpenTelemetry's browser instrumentation wraps fetch and XMLHttpRequest; Sentry's Browser SDK once shipped a dedicated jQuery AJAX integration. Both send spans to a collector, which is the same idea as $.post('/jsprofiler/jsonlogger.php', { json: …, type: 'jquery' }) — standardised, sampled, with a real backend instead of a Smarty template.
What did not survive is the hook. jQuery.event.handle was an internal of the 1.3-era event system and is gone from jQuery 1.9 onwards, so the wrapper no longer has anything to attach to. A 2026 version would instrument jQuery.event.dispatch or, better, leave the library alone and use the platform APIs. The trade — 1 ms clock, one row per jQuery call in MySQL, server-rendered reports — was reasonable for 2009 shared hosting, and is exactly what one would not build today.
Links
Where to find this one
