Collected Metrics

Relay collects the following metrics:

buffer.dequeue_attempts (Histogram)

Number of attempts needed to dequeue spooled envelopes from disk.

As long as there are enough permits in the [crate::utils::BufferGuard], this number should always be one.

buffer.disk_size (Histogram)

The file size of the buffer db on disk, in bytes.

This metric is computed by multiplying page_count * page_size.

buffer.envelopes_disk_count (Gauge)

The number of envelopes waiting for project states on disk.

Note this metric will not be logged when we encounter envelopes in the database on startup, because counting those envelopes reliably would risk locking the db for multiple seconds.

The disk buffer size can be configured with spool.envelopes.max_disk_size.

buffer.envelopes_mem (Histogram)

The estimated number of envelope bytes buffered in memory.

The memory buffer size can be configured with spool.envelopes.max_memory_size.

buffer.envelopes_mem_count (Gauge)

The number of envelopes waiting for project states in memory.

This number is always <= EnvelopeQueueSize.

The memory buffer size can be configured with spool.envelopes.max_memory_size.

buffer.envelopes_read (Counter)

Number of envelopes the envelope buffer reads back from disk.

buffer.envelopes_written (Counter)

Number of envelopes the envelope buffer spools to disk.

buffer.message.duration (Timer)

Timing in milliseconds for processing a message in the buffer service.

This metric is tagged with:

  • message: The type of message that was processed.
buffer.reads (Counter)

Number times the envelope buffer reads back from disk.

buffer.writes (Counter)

Number times the envelope buffer spools to disk.

dynamic_sampling_decision (Counter)

Counter for dynamic sampling decision.

This metric is tagged with:

  • decision: "drop" if dynamic sampling drops the envelope, else "keep".
event.accepted (Counter)

Number of envelopes accepted in the current time slot.

This represents requests that have successfully passed rate limits and filters, and have been sent to the upstream.

This metric is tagged with:

  • handling: Either "success" if the envelope was handled correctly, or "failure" if there was an error or bug.
event.corrupted (Counter)

Number of Events that had corrupted (unprintable) event attributes.

This currently checks for environment and release, for which we know that some SDKs may send corrupted values.

event.item_size (Histogram)

The number of bytes received by Relay for each individual envelope item type.

Metric is tagged by the item type.

event.opentelemetry (Counter)

Number of Events with an OpenTelemetry Context

This metric is tagged with:

  • platform: The event's platform, such as "javascript".
  • sdk: The name of the Sentry SDK sending the transaction. This tag is only set for Sentry's SDKs and defaults to "proprietary".
event.processing_time (Timer)

Time in milliseconds spent in synchronous processing of envelopes.

This timing covers the end-to-end processing in the CPU pool and comprises:

  • event_processing.deserialize
  • event_processing.pii
  • event_processing.serialization

With Relay in processing mode, this also includes the following timings:

  • event_processing.process
  • event_processing.filtering
  • event_processing.rate_limiting
event.protocol (Counter)

Number of events that hit any of the store-like endpoints: Envelope, Store, Security, Minidump, Unreal.

The events are counted before they are rate limited, filtered, or processed in any way.

This metric is tagged with:

  • version: The event protocol version number defaulting to 7.
event.queue_size (Histogram)

The number of envelopes in the queue.

The queue holds all envelopes that are being processed at a particular time in Relay:

  • When Relay receives a Request, it ensures the submitted data is wrapped in a single envelope.
  • The envelope receives some preliminary processing to determine if it can be processed or if it must be rejected.
  • Once this determination has been made, the HTTP request that created the envelope terminates and, if the request is to be further processed, the envelope enters a queue.
  • After the envelope finishes processing and is sent upstream, the envelope is considered handled and it leaves the queue.

The queue size can be configured with cache.event_buffer_size.

event.queue_size.pct (Histogram)

The number of envelopes in the queue as a percentage of the maximum number of envelopes that can be stored in the queue.

The value ranges from 0 when the queue is empty to 1 when the queue is full and no additional events can be added. The queue size can be configured using event.queue_size.

event.rejected (Counter)

Number of envelopes rejected in the current time slot.

This includes envelopes being rejected because they are malformed or any other errors during processing (including filtered events, invalid payloads, and rate limits).

To check the rejection reason, check events.outcomes, instead.

This metric is tagged with:

  • handling: Either "success" if the envelope was handled correctly, or "failure" if there was an error or bug.
event.spans (Histogram)

The number of spans per processed transaction event.

This metric is tagged with:

  • platform: The event's platform, such as "javascript".
  • sdk: The name of the Sentry SDK sending the transaction. This tag is only set for Sentry's SDKs and defaults to "proprietary".
event.total_time (Timer)

Total time in milliseconds an envelope spends in Relay from the time it is received until it finishes processing and has been submitted to the upstream.

event.transaction (Counter)

The number of transaction events processed by the source of the transaction name.

This metric is tagged with:

  • platform: The event's platform, such as "javascript".
  • source: The source of the transaction name on the client. See the transaction source documentation for all valid values.
  • contains_slashes: Whether the transaction name contains /. We use this as a heuristic to represent URL transactions.
event.transaction_name_changes (Counter)

The number of transaction events processed grouped by transaction name modifications. This metric is tagged with:

  • source_in: The source of the transaction name before normalization. See the transaction source documentation for all valid values.
  • change: The mechanism that changed the transaction name. Either "none", "pattern", "rule", or "both".
  • source_out: The source of the transaction name after normalization.
event.wait_time (Timer)

Time spent between the start of request handling and processing of the envelope.

This includes streaming the request body, scheduling overheads, project config fetching, batched requests and congestions in the internal processor. This does not include delays in the incoming request (body upload) and skips all envelopes that are fast-rejected.

event_processing.deserialize (Timer)

Time in milliseconds spent deserializing an event from JSON bytes into the native data structure on which Relay operates.

event_processing.filtering (Timer)

Time in milliseconds spent running inbound data filters on an event.

event_processing.pii (Timer)

Time in milliseconds spent in data scrubbing for the current event. Data scrubbing happens last before serializing the event back to JSON.

event_processing.process (Timer)

Time in milliseconds spent running event processors on an event for normalization. Event processing happens before filtering.

event_processing.rate_limiting (Timer)

Time in milliseconds spent checking for organization, project, and DSN rate limits.

Not all events reach this point. After an event is rate limited for the first time, the rate limit is cached. Events coming in after this will be discarded earlier in the request queue and do not reach the processing queue.

event_processing.serialization (Timer)

Time spent converting the event from its in-memory reprsentation into a JSON string.

events.outcomes (Counter)

Number of outcomes and reasons for rejected Envelopes.

This metric is tagged with:

  • outcome: The basic cause for rejecting the event.
  • reason: A more detailed identifier describing the rule or mechanism leading to the outcome.
  • to: Describes the destination of the outcome. Can be either 'kafka' (when in processing mode) or 'http' (when outcomes are enabled in an external relay).

Possible outcomes are:

  • filtered: Dropped by inbound data filters. The reason specifies the filter that matched.
  • rate_limited: Dropped by organization, project, or DSN rate limit, as well as exceeding the Sentry plan quota. The reason contains the rate limit or quota that was exceeded.
  • invalid: Data was considered invalid and could not be recovered. The reason indicates the validation that failed.
global_config.fetch (Counter)

Number of global config fetches from upstream. Only 2XX responses are considered and ignores send errors (e.g. auth or network errors).

This metric is tagged with:

  • success: whether deserializing the global config succeeded.
global_config.requests.duration (Timer)

Total time spent to send a request and receive the response from upstream.

health.message.duration (Timer)

Timing in milliseconds for handling and responding to a health check request.

This metric is tagged with:

  • type: The type of the health check, liveness or readiness.
health.system_memory.total (Gauge)

The total system memory.

Relay uses the same value for its memory health check.

health.system_memory.used (Gauge)

The currently used memory by the entire system.

Relay uses the same value for its memory health check.

http_queue.size (Histogram)

The number of upstream requests queued up for sending.

Relay employs connection keep-alive whenever possible. Connections are kept open for 15 seconds of inactivity or 75 seconds of activity. If all connections are busy, they are queued, which is reflected in this metric.

This metric is tagged with:

  • priority: The queueing priority of the request, either "high" or "low". The priority determines precedence in executing requests.

The number of concurrent connections can be configured with:

  • limits.max_concurrent_requests for the overall number of connections
  • limits.max_concurrent_queries for the number of concurrent high-priority requests
kafka.message_size (Histogram)

Size of emitted kafka message in bytes, tagged by message type.

metrics.aggregator.message.duration (Timer)

Timing in milliseconds for processing a message in the aggregator service.

This metric is tagged with:

  • message: The type of message that was processed.
metrics.buckets (Gauge)

The total number of metric buckets in Relay's metrics aggregator.

This metric is tagged with:

  • aggregator: The name of the metrics aggregator (usually "default").
metrics.buckets.batches_per_partition (Histogram)

The number of batches emitted per partition.

metrics.buckets.cost (Gauge)

The total storage cost of metric buckets in Relay's metrics aggregator.

This metric is tagged with:

  • aggregator: The name of the metrics aggregator (usually "default").
metrics.buckets.created.unique (Set)

Count the number of unique buckets created.

This is a set of bucketing keys. The metric is basically equivalent to metrics.buckets.merge.miss for a single Relay, but could be useful to determine how much duplicate buckets there are when multiple instances are running.

The hashing is platform-dependent at the moment, so all your relays that send this metric should run on the same CPU architecture, otherwise this metric is not reliable.

This metric is tagged with:

  • aggregator: The name of the metrics aggregator (usually "default").
  • namespace: The namespace of the metric for which the bucket was created.
metrics.buckets.delay (Histogram)

The reporting delay at which a bucket arrives in Relay.

A positive delay indicates the bucket arrives after its stated timestamp. Large delays indicate backdating, particularly all delays larger than bucket_interval + initial_delay. Negative delays indicate that the bucket is dated into the future, likely due to clock drift on the client.

This metric is tagged with:

  • backdated: A flag indicating whether the metric was reported within the initial_delay time period (false) or after the initial delay has expired (true).
metrics.buckets.dropped (Counter)

Incremented every time a bucket is dropped.

This should only happen when a project state is invalid during graceful shutdown.

This metric is tagged with:

  • aggregator: The name of the metrics aggregator (usually "default").
metrics.buckets.flushed (Histogram)

The total number of metric buckets flushed in a cycle across all projects.

This metric is tagged with:

  • aggregator: The name of the metrics aggregator (usually "default").
metrics.buckets.flushed_per_project (Histogram)

The number of metric buckets flushed in a cycle for each project.

Relay scans metric buckets in regular intervals and flushes expired buckets. This histogram is logged for each project that is being flushed. The count of the histogram values is equivalent to the number of projects being flushed.

This metric is tagged with:

  • aggregator: The name of the metrics aggregator (usually "default").
metrics.buckets.invalid_timestamp (Histogram)

Distribution of invalid bucket timestamps observed, relative to the time of observation.

This is a temporary metric to better understand why we see so many invalid timestamp errors.

metrics.buckets.merge.hit (Counter)

Incremented every time two buckets or two metrics are merged.

This metric is tagged with:

  • aggregator: The name of the metrics aggregator (usually "default").
  • namespace: The namespace of the metric.
metrics.buckets.merge.miss (Counter)

Incremented every time a bucket is created.

This metric is tagged with:

  • aggregator: The name of the metrics aggregator (usually "default").
  • namespace: The namespace of the metric.
metrics.buckets.parsing_failed (Counter)

Number of times that parsing a metrics bucket item from an envelope failed.

metrics.buckets.partition_keys (Histogram)

Distribution of flush buckets over partition keys.

The distribution of buckets should be even. If it is not, this metric should expose it.

metrics.buckets.per_batch (Histogram)

The number of buckets in a batch emitted.

This corresponds to the number of buckets that will end up in an envelope.

metrics.buckets.scan_duration (Timer)

Time in milliseconds spent scanning metric buckets to flush.

Relay scans metric buckets in regular intervals and flushes expired buckets. This timer shows the time it takes to perform this scan and remove the buckets from the internal cache. Sending the metric buckets to upstream is outside of this timer.

This metric is tagged with:

  • aggregator: The name of the metrics aggregator (usually "default").
metrics.buckets.size (Gauge)

The average number of elements in a bucket when flushed.

This metric is tagged with:

  • metric_type: "c", "d", "g" or "s".
  • namespace: The namespace of the metric.
metrics.meta.agg.items (Counter)

Incremnted for every metric meta item added to the metric meta aggregator.

metrics.meta.agg.update (Counter)

Incremented every time the meta aggregator emitted an update that needs to be stored or sent upstream.

metrics.meta.parsing_failed (Counter)

Number of times that parsing a metric meta item from an envelope failed.

metrics.meta.redis.update (Counter)

Incremented every time a redis key is updated to store or update metadata.

metrics.transaction_name (Counter)

Count extraction of transaction names. Tag with the decision to drop / replace / use original.

outcomes.aggregator.flush_time (Timer)

The time it takes the outcome aggregator to flush aggregated outcomes.

processing.event.produced (Counter)

Number of messages placed on the Kafka queues.

When Relay operates as Sentry service and an Envelope item is successfully processed, each Envelope item results in a dedicated message on one of the ingestion topics on Kafka.

This metric is tagged with:

  • event_type: The kind of message produced to Kafka.
  • namespace (only for metrics): The namespace that the metric belongs to.
  • is_segment (only for event_type span): true the span is the root of a segment.
  • has_parent (only for event_type span): false if the span is the root of a trace.
  • platform (only for event_type span): The platform from which the span was spent.
  • metric_type (only for event_type metric): The metric type, counter, distribution, gauge or set.
  • metric_encoding (only for event_type metric): The encoding used for distribution and set metrics.

The message types can be:

  • event: An error or transaction event. Error events are sent to ingest-events, transactions to ingest-transactions, and errors with attachments are sent to ingest-attachments.
  • attachment: An attachment file associated with an error event, sent to ingest-attachments.
  • user_report: A message from the user feedback dialog, sent to ingest-events.
  • session: A release health session update, sent to ingest-sessions.
processing.produce.error (Counter)

Number of producer errors occurred after an envelope was already enqueued for sending to Kafka.

These errors include, for example, "MessageTooLarge" errors when the broker does not accept the requests over a certain size, which is usually due to invalid or inconsistent broker/producer configurations.

This metric is tagged with:

  • topic: The Kafka topic being produced to.
processor.batched_metrics.calls (Counter)

Number of times the batched metrics processing has been called with at least one bucket of a namespace.

This metric is tagged with:

  • namespace: the metric namespace.
processor.batched_metrics.cost (Counter)

Bucket cost for all buckets processed in the batched metrics handling of the envelope processor.

This metric is tagged with:

  • namespace: the metric namespace.
processor.batched_metrics.count (Counter)

Number of buckets processed in the batched metrics handling of the envelope processor.

This metric is tagged with:

  • namespace: the metric namespace.
processor.encode_metrics.calls (Counter)

Number of times the metric encoding has been called with at least one bucket of a namespace.

This metric is tagged with:

  • namespace: the metric namespace.
processor.encode_metrics.cost (Counter)

Bucket cost for all buckets encoded in the envelope processor.

This metric is tagged with:

  • namespace: the metric namespace.
processor.encode_metrics.count (Counter)

Number of metric buckets encoded in the envelope processor.

This metric is tagged with:

  • namespace: the metric namespace.
processor.message.duration (Timer)

Timing in milliseconds for processing a message in the internal CPU pool.

This metric is tagged with:

  • message: The type of message that was processed.
processor.rate_limit_buckets.calls (Counter)

Number of times metric bucket rate limiting has been called with at least one bucket of a namespace.

This metric is tagged with:

  • namespace: the metric namespace.
processor.rate_limit_buckets.cost (Counter)

Bucket cost for all buckets rate limited envelope processor.

This metric is tagged with:

  • namespace: the metric namespace.
processor.rate_limit_buckets.count (Counter)

Number of metric buckets rate limited in the envelope processor.

This metric is tagged with:

  • namespace: the metric namespace.
project_cache.eviction (Counter)

Number of evicted stale projects from the cache.

Relay scans the in-memory project cache for stale entries in a regular interval configured by cache.eviction_interval.

The cache duration for project states can be configured with the following options:

  • cache.project_expiry: The time after which a project state counts as expired. It is automatically refreshed if a request references the project after it has expired.
  • cache.project_grace_period: The time after expiry at which the project state will still be used to ingest events. Once the grace period expires, the cache is evicted and new requests wait for an update.
project_cache.garbage.queue_size (Gauge)

The number of items currently in the garbage disposal queue.

project_cache.hit (Counter)

Number of times a project is looked up from the cache.

The cache may contain and outdated or expired project state. In that case, the project state is updated even after a cache hit.

project_cache.message.duration (Timer)

Timing in milliseconds for handling a project cache message.

This metric is tagged with:

  • message: The type of message that was processed.
project_cache.miss (Counter)

Number of times a project lookup failed.

A cache entry is created immediately and the project state requested from the upstream.

project_cache.size (Histogram)

Number of project states currently held in the in-memory project cache.

The cache duration for project states can be configured with the following options:

  • cache.project_expiry: The time after which a project state counts as expired. It is automatically refreshed if a request references the project after it has expired.
  • cache.project_grace_period: The time after expiry at which the project state will still be used to ingest events. Once the grace period expires, the cache is evicted and new requests wait for an update.

There is no limit to the number of cached projects.

project_cache.task.duration (Timer)

Timing in milliseconds for processing a task in the project cache service.

A task is a unit of work the service does. Each branch of the tokio::select is a different task type.

This metric is tagged with:

  • task: The type of the task the processor does.
project_state.attempts (Histogram)

Number of attempts required to fetch the config for a given project key.

project_state.decompression (Timer)

Time in milliseconds required to decompress a project config from redis.

Note that this also times the cases where project config is uncompressed, in which case the timer should be very close to zero.

project_state.eviction.duration (Timer)

Total time in milliseconds spent evicting outdated and unused projects happens.

project_state.flush_all_metric_meta (Counter)

Number of full metric data flushes.

A full flush takes all contained items of the aggregator and flushes them upstream, at best this happens once per freshly loaded project.

project_state.get (Counter)

Number of times a project state is looked up from the cache.

This includes lookups for both cached and new projects. As part of this, updates for outdated or expired project caches are triggered.

Related metrics:

  • project_cache.hit: For successful cache lookups, even for outdated projects.
  • project_cache.miss: For failed lookups resulting in an update.
project_state.no_cache (Counter)

Number of times a project config was requested with .no-cache.

This effectively counts the number of envelopes or events that have been sent with a corresponding DSN. Actual queries to the upstream may still be deduplicated for these project state requests.

A maximum of 1 such requests per second is allowed per project key. This metric counts only permitted requests.

project_state.pending (Histogram)

Number of projects in the in-memory project cache that are waiting for their state to be updated.

See project_cache.size for more description of the project cache.

project_state.received (Histogram)

Number of project states returned from the upstream for each batch request.

If multiple batches are updated concurrently, this metric is reported multiple times.

See project_cache.size for more description of the project cache.

project_state.redis.requests (Counter)

Number of times a project state is requested from the central Redis cache.

This has a tag hit with values true or false. If false the request will be sent to the sentry endpoint.

project_state.request (Counter)

Number of project state HTTP requests.

Relay updates projects in batches. Every update cycle, Relay requests limits.max_concurrent_queries batches of cache.batch_size projects from the upstream. The duration of these requests is reported via project_state.request.duration.

Note that after an update loop has completed, there may be more projects pending updates. This is indicated by project_state.pending.

project_state.request.batch_size (Histogram)

Number of project states requested from the upstream for each batch request.

If multiple batches are updated concurrently, this metric is reported multiple times.

The batch size can be configured with cache.batch_size. See project_cache.size for more description of the project cache.

project_state.request.duration (Timer)

Total time in milliseconds spent fetching queued project configuration updates requests to resolve.

Relay updates projects in batches. Every update cycle, Relay requests limits.max_concurrent_queries * cache.batch_size projects from the upstream. This metric measures the wall clock time for all concurrent requests in this loop.

Note that after an update loop has completed, there may be more projects pending updates. This is indicated by project_state.pending.

project_upstream.completed (Counter)

Number of times an upstream request for a project config is completed.

Completion can be because a result was returned or because the config request was dropped after there still was no response after a timeout. This metrics has tags for result and attempts indicating whether it was succesful or a timeout and how many attempts were made respectively.

project_upstream.failed (Counter)

Number of times an upstream request for a project config failed.

Failure can happen, for example, when there's a network error. Refer to UpstreamRequestError for all cases.

replay.recording.process (Timer)

Time in milliseconds spent on parsing, normalizing and scrubbing replay recordings.

requests (Counter)

Number of HTTP requests reaching Relay.

requests.duration (Timer)

Total duration in milliseconds for handling inbound web requests until the HTTP response is returned to the client.

This does not correspond to the full event ingestion time. Requests for events that are not immediately rejected due to bad data or cached rate limits always return 200 OK. Full validation and normalization occur asynchronously, which is reported by event.processing_time.

This metric is tagged with:

  • method: The HTTP method of the request.
  • route: Unique dashed identifier of the endpoint.
requests.timestamp_delay (Timer)

The delay between the timestamp stated in a payload and the receive time.

SDKs cannot transmit payloads immediately in all cases. Sometimes, crashes require that events are sent after restarting the application. Similarly, SDKs buffer events during network downtimes for later transmission. This metric measures the delay between the time of the event and the time it arrives in Relay. The delay is measured after clock drift correction is applied.

Only payloads with a delay of more than 1 minute are captured.

This metric is tagged with:

  • category: The data category of the payload. Can be one of: event, transaction, security, or session.
responses.status_codes (Counter)

Number of completed HTTP requests.

This metric is tagged with:

  • status_code: The HTTP status code number.
  • method: The HTTP method used in the request in uppercase.
  • route: Unique dashed identifier of the endpoint.
scrubbing.attachments.duration (Timer)

Time spend on attachment scrubbing.

This represents the total time spent on evaluating the scrubbing rules for an attachment and the attachment scrubbing itself, regardless of whether any rules were applied. Note that minidumps which failed to be parsed (status="error" in scrubbing.minidumps.duration) will be scrubbed as plain attachments and count towards this.

scrubbing.minidumps.duration (Timer)

Time spent on minidump scrubbing.

This is the total time spent on parsing and scrubbing the minidump. Even if no PII scrubbing rules applied the minidump will still be parsed and the rules evaluated on the parsed minidump, this duration is reported here with status of "n/a".

This metric is tagged with:

  • status: Scrubbing status: "ok" means successful scrubbed, "error" means there was an error during scrubbing and finally "n/a" means scrubbing was successful but no scurbbing rules applied.
server.starting (Counter)

Number of Relay server starts.

This can be used to track unwanted restarts due to crashes or termination.

service.back_pressure (Gauge)

A number of messages queued in a services inbound message channel.

This metric is emitted once per second for every running service. Without backlogs, this number should be close to 0. If this number is monotonically increasing, the service is not able to process the inbound message volume.

This metric is tagged with:

  • service: The fully qualified type name of the service implementation.
upstream.envelope.body_size (Histogram)

Size of queries (projectconfig queries, i.e. the request payload, not the response) sent by Relay over HTTP in bytes.

upstream.metrics.body_size (Histogram)

Size of batched global metrics requests sent by Relay over HTTP in bytes.

upstream.network_outage (Gauge)

The state of Relay with respect to the upstream connection. Possible values are 0 for normal operations and 1 for a network outage.

upstream.query.body_size (Histogram)

Size of envelopes sent over HTTP in bytes.

upstream.requests.duration (Timer)

Total time spent to send request to upstream Relay and handle the response.

This metric is tagged with:

  • result: What happened to the request, an enumeration with the following values:
  • success: The request was sent and returned a success code HTTP 2xx
  • response_error: The request was sent and it returned an HTTP error.
  • payload_failed: The request was sent but there was an error in interpreting the response.
  • send_failed: Failed to send the request due to a network error.
  • rate_limited: The request was rate limited.
  • invalid_json: The response could not be parsed back into JSON.
  • route: The endpoint that was called on the upstream.
  • status-code: The status code of the request when available, otherwise "-".
  • retries: Number of retries bucket 0, 1, 2, few (3 - 10), many (more than 10).
upstream.retries (Histogram)

Counts the number of retries for each upstream http request.

This metric is tagged with:

  • result: What happened to the request, an enumeration with the following values:
  • success: The request was sent and returned a success code HTTP 2xx
  • response_error: The request was sent and it returned an HTTP error.
  • payload_failed: The request was sent but there was an error in interpreting the response.
  • send_failed: Failed to send the request due to a network error.
  • rate_limited: The request was rate limited.
  • invalid_json: The response could not be parsed back into JSON.
  • route: The endpoint that was called on the upstream.
  • status-code: The status code of the request when available, otherwise "-".
Help improve this content
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").