npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

crow-timeseries

v1.1.0

Published

a very tiny inline CDN

Readme

crow-timeseries

Crow-timeseries is a pea-sized timeseries database for single-instance servers, connecting crow-metrics (or the metrics library of your choice) with yeri to store your server's historical metric data and display them with flexibly-configured SVG graphs.

It uses sqlite3 for storing the timeseries data.

Build

npm install
npm test

Use

This sample code creates a new crow Metrics object, attaches it to a timeseries database, and generates an SVG graph of median and 99% response times for web requests over the past day:

import { Metrics } from "crow-metrics";
import { TimeSeriesDatabase } from "crow-timeseries";

// create a new crow metrics instance for collecting and reporting metrics.
// make sure a distribution named `web-response` exists. (we assume timings
// are reported elsewhere in the code.)
const metrics = Metrics.create();
const responseTime = metrics.distribution("web-response");

// attach crow to our timeseries database to collect the minutely reports.
// the database will be created and initialized if it doesn't exist yet.
const db = await TimeSeriesDatabase.build("./data/timeseries.db");
db.attach(metrics);

// generate an SVG graph of median & 99th-percentile web response times over
// the past day.
const svg = await db.generateSvgGraph(
  [
    "web-response{p=0.5}:name('median'):coalesce(max)",
    "web-response{p=0.99}:name('99th percentile'):coalesce(max)",
  ],
  { units: "sec" },
  "day"
);

The resulting image may look something like:

API

TimeSeriesDatabase.build(filename: string, public options: { log?: (message: string) => void, init?: boolean }): Promise<TimeSeriesDatabase>

Open a time series database from a sqlite file. If the file doesn't exist, or the init option is set, the initial schema and tables will be created too. If a log function is passed in, it's used to log when a snapshot is ingested.

db.close()

Close the database file (for a clean shutdown).

db.attach(metrics: Metrics)

Attach to this crow Metrics object, receive the minutely snapshots, and automatically ingest them into the timeseries database. This just forwards the snapshot object to add().

db.add(snapshot: Snapshot)

Ingest the (delta) metric values from a crow snapshot.

db.addRaw(timestamp: number, metrics: RawMetric[])

If you don't use crow, call this method periodically to ingest new metric values. Each RawMetric has these fields:

  • name: string
  • type: "counter" | "gauge" | "distribution"
  • value: number

Check the crow documentation for an explanation of the counter, gauge, and distribution types.

db.getMetricNames(): Name[]

Return a list of all the metric names that have appeared in the database. Each Name has these fields:

  • key: number
  • name: string
  • type: string

The key is unique to the name and is just used to optimize database storage. (Each name is only stored once in the database. All metrics are saved as tuples of timestamp, key, and value.) The name and type are the same as used in addRaw().

db.getMetric(name: string, start: number, end: number): TimeSeries

Fetch time series data for the named metric between start (inclusive) and end (inclusive). The start and end times are given in epoch seconds, not javascript milliseconds. TimeSeries is a class from yeri that includes a name, a timestamps array (in seconds), and a values array.

db.exportOldMetrics(before: number, otherDb: TimeSeriesDatabase, options: { deleteAfter?: boolean } = {}): number

Collect all metrics with a timestamp before before (in epoch seconds) and write them into another database file. Optionally, after copying the metrics over, delete them from this database. This can be useful if you want to keep one primary database for "current" metrics small and fast, and another one as an archive. Returns the number of rows deleted, if deleteAfter was true.

db.exportOldMetricsCsv(before: number, filename: string, options: { deleteAfter?: boolean } = {}): Promise<number>

Same as exportOldMetrics, but writes the old metrics into a CSV (text) file.

db.generateSvgGraph(metrics: GraphPlan[] | string[], options: GraphOptions, timeSpan: TimeSpan, now?: number): Promise<string>

Render one or more time series into an SVG image using yeri.

Each metric will receive its own line (and color), and is described using a string expression. The expression syntax is shown below. You may optionally pre-compile the expressions into GraphPlans.

Options has these fields:

  • timezone?: string

    Timezone to use for the x (time) axis labels. Uses "local time" if left blank.

  • coalesceInterval?: number

    Number of seconds to coalesce data points (default 60).

  • lineWidth?: number

    Set the stroke width of lines in the graph (default: 7).

  • dotWidth?: number

    Set the width of a dot in the graph, when highlighting a point in a tooltip (default: 15).

  • units?: string

    Units to use on the y axis for values (optional))

  • binary?: boolean

    If true, the units become milli/kilo/mega (with prefix letters) using powers of 1024 instead of 1000. This is used for computer units like bytes (of memory or disk).

Time span is one of:

  • "hour" - an hour ago until now, coalesced to each minute (61 points per graph)
  • "day" - 24 hours ago until now, coalesced every 5 minutes (289 points per graph)
  • "7day" - 7 days ago until now, coalesced every 30 minutes (337 points per graph)
  • "30day" - 30 days ago until now, coalesced every 2 hours (361 points per graph)
  • or, an object with fields for setting a specific time span:
    • startTime: number - an epoch time in seconds, inclusive
    • endTime: number - an epoch time in seconds, inclusive
    • interval: number - number of seconds to coalesce points across

Coalescing data points combines every value within an interval into a single value. For example, if you store metrics every 15 seconds, then a coalesce interval of 60 will combine every set of 4 data values into 1. The coalescing strategy can be set on each metric as an option in its expression, and defaults to choosing the first point.

Unless an explicit start and end time are given, the time span ends at now, which will be the current time (in seconds) unless overridden.

generateGraphData(metrics: GraphPlan[] | string[], options: GraphOptions, timeSpan: TimeSpan, now?: number): Promise<GraphData>

Follow the same procedure as generateSvgGraph, but instead of rendering a graph, return all the processed data that would have been used to generate the graph. GraphData has these fields:

  • timeSeriesList: TimeSeries[]

    Values fetched from the database, with a TimeSeries for each of the metrics in metrics. TimeSeries is a class from yeri that includes a name, a timestamps array (in seconds), and a values array.

  • graphConfig: Partial<SvgGraphConfig>

    Configuration for drawing the graph (colors, line widths, and so on) that can be passed directly to yeri's SVG generating functions.

  • startTime: number

  • endTime: number

  • interval: number

    The start, end, and coalesce intervals (in seconds) computed from timeSpan.

Metrics graphing expressions

A metrics expression is either a single named metric (like job-queue-size) or a function of multiple named metrics that will be combined into a single time series. It may be followed by options (separated by ":") for setting things like the color, fill, or coalesce strategy.

For those who like formal descriptions:

metric-expr := expr (":" option)*
expr := metric-name | function-name ("(" (expr ","?)* ")")?

metric-name := /^[A-Za-z][A-Za-z0-9\{\}\=\._-]*$/

function-name := "sum" | "max" | "min" | "count" | "diff"

option := "fill" |
    "name(" string ")" |
    "color(" color ")" |
    "coalesce(" coalesce-strategy ")" |
    "interval(" number ")" |
    "scale(" number ")"

coalesce-strategy := "min" | "max" | "average" | "median" | "first" | "last" | "sum"

color := "#" /[0-9A-Fa-f]{3,6}/ | color-name | string
string := /'[^']*'/

For example, if you had two gauges named workers-active and workers-busy, you could draw a red line showing the maximum busy workers across time as:

workers-busy:coalesce(max):color(red)

And you could compute the idle workers by subtracting busy workers from the total active workers:

diff(workers-active, workers-busy):coalesce(min)

Each function takes 2 or more expressions as arguments, separated by commas. The arguments may be also be function calls.

The functions, options, and coalesce strategies are described below.

Functions

Since the arguments to functions are themselves time series (either individual metrics or other functions), the function is applied at each point in time, across the values of the arguments, to generate a new time series. For example, if time series A is (10, 11, 12) and time series B is (20, 30, 40), then the "sum" function across them will return the new time series (30, 41, 52).

  • sum - add all of the nested expressions together
  • max - use only the maximum value of the nested expressions at each time
  • min - use only the minimum value of the nested expressions at each time
  • count - generate a time series of the count of nested expressions that had a defined value at each time
  • diff - subtract the 2nd and all following expressions from the 1st expression

Options

  • fill - Fill in the area under the line when graphing.
  • name(...) - Use this name in the legend of the graph. Defaults to the expression string.
  • color(...) - Set the color of the line & fill. This can be a hex code (starting with "#"), a common color name like "blue", or a string in quotes ('string here') which will be inserted directly into the SVG.
  • coalesce - Set the strategy to use when combining multiple values into one point. (See below for a description of the coalesce strategies.)
  • interval - Override the time interval between points (in seconds). Any values within the same interval will be coalesced.
  • scale - Multiply the value by this constant before graphing. This can be useful if the metric is stored is milliseconds, but you'd like the graph axis to be labeled in seconds (sec, msec, usec, Ksec, ...).

Coalesce strategies

  • min - minimum of the values (10, 20, 36 -> 10)
  • max - maximum of the values (10, 20, 36 -> 36)
  • average - average of the values (10, 20, 36 -> 22)
  • median - median of the values (10, 20, 36 -> 20)
  • first - the value with the earliest timestamp
  • last - the value with the latest timestamp
  • sum - sum of the values (10, 20, 36 -> 66)

To-do

  • API call to truncate the database to wipe data older than a certain timestamp
  • API call to export older data as a new sqlite3 database or csv
  • allow simple constant math (like "/ 1000") on metrics in expressions?

License

This code is licensed under either the prosperity license (LICENSE-prosperity.md) or the anti-capitalist license (LICENSE-anti-capitalist.txt) at your preference, for personal or non-commercial use.

Authors