Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add build_info data #52

Merged
merged 10 commits into from
May 12, 2023
46 changes: 28 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,28 +49,12 @@ The Autometrics library:
- Uses a Prometheus Exporter to write metrics to a `/metrics` endpoint (by
default on port `:9464`) or pushes them to a specified gateway (if used in
browser)
- Uses a TypeScript plugin to automatically add useful Prometheus queries in the doc comments for instrumented functions
- Uses a TypeScript plugin / VSCode extension to automatically add useful Prometheus queries in the doc comments for instrumented functions

## Quickstart

```shell
npm install --save autometrics
npm install --save-dev @autometrics/typescript-plugin
```

Enable the TypeScript plugin by adding it to `tsconfig.json`:

```json
{
"compilerOptions": {
"plugins": [
{
"name": "@autometrics/typescript-plugin",
"prometheusUrl": ""
}
]
}
}
```

Use the library in your code:
Expand Down Expand Up @@ -98,10 +82,27 @@ The default `autometrics` package bundles `@opentelemetry/sdk-metrics` and
these in your codebase or want to use other custom metrics, use the following
installation option.

Install the wrappers and the language service plugin:
Install the wrappers:

```shell
npm install --save @autometrics/autometrics
```

Import and use the library in your code:

```typescript
import { autometrics } from "@autometrics/autometrics"
```

## Getting PromQL queries

In order to get PromQL query links in your IDE download the [Autometrics VSCode
extension](https://marketplace.visualstudio.com/items?itemName=Fiberplane.autometrics).

If you're on any other IDE you can install and add the TypeScript plugin
directly:

```bash
npm install --save-dev @autometrics/typescript-plugin
```

Expand All @@ -124,6 +125,15 @@ Add the language service plugin to the `tsconfig.json` file:

Autometrics provides [Grafana dashboards](https://github.com/autometrics-dev/autometrics-shared#dashboards) that will work for any project instrumented with the library.

## Identifying commits that introduce errors

Autometrics makes it easy to [spot versions and commits that introduce errors or latency](https://fiberplane.com/blog/autometrics-rs-0-4-spot-commits-that-introduce-errors-or-slow-down-your-application).

| Label | Run-Time Environment Variables | Default value |
|---|---|---|
| `version` | `AUTOMETRICS_VERSION` or `PACKAGE_VERSION` | `npm_package_version` (set by npm/yarn/pnpm by default) |
| `commit` | `AUTOMETRICS_COMMIT` or `COMMIT_SHA` | `""` |
| `branch` | `AUTOMETRICS_BRANCH` or `BRANCH_NAME` | `""` |

## Alerts / SLOs

Expand Down
40 changes: 40 additions & 0 deletions packages/autometrics-lib/src/buildInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { getMeter } from "./instrumentation";

type BuildInfo = {
version?: string;
commit?: string;
branch?: string;
};

let buildInfo: BuildInfo | undefined;

export function registerBuildInfo() {
if (buildInfo) {
return;
}

buildInfo = {
version: getVersion(),
commit: getCommit(),
branch: getBranch(),
};

const gauge = getMeter().createUpDownCounter("build_info");
gauge.add(1, buildInfo);
}

function getVersion() {
if (process.env.npm_package_version) {
return process.env.npm_package_version;
}

return process.env.PACKAGE_VERSION || process.env.AUTOMETRICS_VERSION;
}

function getCommit() {
return process.env.COMMIT_SHA || process.env.AUTOMETRICS_COMMIT;
}

function getBranch() {
return process.env.BRANCH_NAME || process.env.AUTOMETRICS_BRANCH;
}
3 changes: 2 additions & 1 deletion packages/autometrics-lib/src/instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
PrometheusSerializer,
} from "@opentelemetry/exporter-prometheus";
import {
AggregationTemporality,
InMemoryMetricExporter,
MeterProvider,
MetricReader,
Expand Down Expand Up @@ -43,7 +44,7 @@ export function init(options: initOptions) {
exporter = new PeriodicExportingMetricReader({
// 0 - using delta aggregation temporality setting
// to ensure data submitted to the gateway is accurate
exporter: new InMemoryMetricExporter(0),
exporter: new InMemoryMetricExporter(AggregationTemporality.DELTA),
});
// Make sure the provider is initialized and exporter is registered
getMetricsProvider();
Expand Down
7 changes: 5 additions & 2 deletions packages/autometrics-lib/src/wrappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getModulePath,
isPromise,
} from "./utils";
import { registerBuildInfo } from "./buildInfo";

// Function Wrapper
// This seems to be the preferred way for defining functions in TypeScript
Expand Down Expand Up @@ -97,9 +98,10 @@ export function autometrics<F extends FunctionSig>(
}

if (!functionName) {
throw new Error(
"Autometrics decorated function must have a name to successfully create a metric",
console.trace(
keturiosakys marked this conversation as resolved.
Show resolved Hide resolved
"Autometrics decorated function must have a name to successfully create a metric. Function will not be instrumented.",
);
return fn;
}

const counterObjectiveAttributes: Attributes = {};
Expand All @@ -124,6 +126,7 @@ export function autometrics<F extends FunctionSig>(

return function (...params) {
const meter = getMeter();
registerBuildInfo();
const autometricsStart = performance.now();
const counter = meter.createCounter("function.calls.count");
const histogram = meter.createHistogram("function.calls.duration");
Expand Down
3 changes: 2 additions & 1 deletion packages/autometrics-lib/tests/integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { autometrics, init } from "../src";
import { describe, test, expect, beforeAll, afterEach } from "vitest";
import {
AggregationTemporality,
InMemoryMetricExporter,
PeriodicExportingMetricReader,
} from "@opentelemetry/sdk-metrics";
Expand All @@ -14,7 +15,7 @@ describe("Autometrics integration test", () => {
exporter = new PeriodicExportingMetricReader({
// 0 - using delta aggregation temporality setting
// to ensure data submitted to the gateway is accurate
exporter: new InMemoryMetricExporter(0),
exporter: new InMemoryMetricExporter(AggregationTemporality.DELTA),
});

init({ exporter });
Expand Down
6 changes: 3 additions & 3 deletions packages/autometrics-typescript-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ function init(modules: { typescript: typeof tsserver }) {
typechecker,
);

const requestRate = createRequestRateQuery(nodeIdentifier, nodeType);
const errorRatio = createErrorRatioQuery(nodeIdentifier, nodeType);
const latency = createLatencyQuery(nodeIdentifier, nodeType);
const requestRate = createRequestRateQuery(nodeIdentifier);
const errorRatio = createErrorRatioQuery(nodeIdentifier);
const latency = createLatencyQuery(nodeIdentifier);

const requestRateUrl = makePrometheusUrl(requestRate, prometheusBase);
const errorRatioUrl = makePrometheusUrl(errorRatio, prometheusBase);
Expand Down
25 changes: 10 additions & 15 deletions packages/autometrics-typescript-plugin/src/queryHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
/* Functions below template creation of relevant queries and encode them in URL */

import type { NodeType } from "./types";
const BUILD_INFO_LABELS =
"* on (instance, job) group_left(version, commit) (last_over_time(build_info[1s]) or on (instance, job) up)";

export function createLatencyQuery(nodeIdentifier: string, nodeType: string) {
const latency = `sum by (le, function, module) (rate(${nodeType}_calls_duration_bucket{${nodeType}="${nodeIdentifier}"}[5m]))`;
return `histogram_quantile(0.99, ${latency}) or histogram_quantile(0.95, ${latency})`;
export function createLatencyQuery(nodeIdentifier: string) {
const latency = `sum by (le, function, module, commit, version) (rate(function_calls_duration_bucket{function="${nodeIdentifier}"}[5m]) ${BUILD_INFO_LABELS})`;
return `histogram_quantile(0.99, ${latency}, \"percentile_latency\", \"99\", \"\",\"\") or histogram_quantile(0.95, ${latency}), \"percentile_latency\", \"95\", \"\", \"\")`;
}

export function createRequestRateQuery(
nodeIdentifier: string,
nodeType: NodeType,
) {
return `sum by (function, module) (rate(${nodeType}_calls_count_total{${nodeType}="${nodeIdentifier}"}[5m]))`;
export function createRequestRateQuery(nodeIdentifier: string) {
return `sum by (function, module, commit, version) (rate(function_calls_count_total{function="${nodeIdentifier}"}[5m]) ${BUILD_INFO_LABELS})`;
}

export function createErrorRatioQuery(
nodeIdentifier: string,
nodeType: NodeType,
) {
const requestQuery = createRequestRateQuery(nodeIdentifier, nodeType);
return `sum by (function, module) (rate(${nodeType}_calls_count_total{${nodeType}="${nodeIdentifier}",result="error"}[5m])) / ${requestQuery}`;
export function createErrorRatioQuery(nodeIdentifier: string) {
const requestQuery = createRequestRateQuery(nodeIdentifier);
return `sum by (function, module, commit, version) (rate(function_calls_count_total{function="${nodeIdentifier}",result="error"}[5m]) ${BUILD_INFO_LABELS}) / ${requestQuery}`;
}

const DEFAULT_URL = "http://localhost:9090/";
Expand Down