Skip to content

Commit

Permalink
Submit repository fields with build_info
Browse files Browse the repository at this point in the history
  • Loading branch information
arendjr committed Nov 3, 2023
1 parent 93cedfd commit 0e9eb37
Show file tree
Hide file tree
Showing 11 changed files with 426 additions and 83 deletions.
90 changes: 73 additions & 17 deletions packages/autometrics/src/buildInfo.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
import type { UpDownCounter } from "$otel/api";

import { BUILD_INFO_DESCRIPTION, BUILD_INFO_NAME } from "./constants.ts";
import {
AUTOMETRICS_VERSION_LABEL,
BRANCH_LABEL,
BUILD_INFO_DESCRIPTION,
BUILD_INFO_NAME,
COMMIT_LABEL,
REPOSITORY_PROVIDER_LABEL,
REPOSITORY_URL_LABEL,
VERSION_LABEL,
} from "./constants.ts";
import { getMeter } from "./instrumentation.ts";
import { debug } from "./logger.ts";
import { getBranch, getCommit, getVersion } from "./platform.deno.ts";
import {
getBranch,
getCommit,
getRepositoryProvider,
getRepositoryUrl,
getVersion,
} from "./platform.deno.ts";
import { detectRepositoryProvider } from "./utils.ts";

/**
* BuildInfo is used to create the `build_info` metric that helps to identify
Expand All @@ -14,28 +30,46 @@ import { getBranch, getCommit, getVersion } from "./platform.deno.ts";
*/
export type BuildInfo = {
/**
* The current version of the application.
* The version of the Autometrics specification supported by this library.
*
* Should be set through the `AUTOMETRICS_VERSION` environment variable, or by
* This is set automatically by `autometrics-ts` and you should not override
* this unless you know what you are doing.
*/
[AUTOMETRICS_VERSION_LABEL]?: string;

/**
* The current commit hash of the application.
*
* Should be set through the `AUTOMETRICS_BRANCH` environment variable, or by
* explicitly specifying the `buildInfo` when calling `init()`.
*/
version?: string;
[BRANCH_LABEL]?: string;

/**
* The current commit hash of the application.
*
* Should be set through the `AUTOMETRICS_COMMIT` environment variable, or by
* explicitly specifying the `buildInfo` when calling `init()`.
*/
commit?: string;
[COMMIT_LABEL]?: string;

/**
* The current commit hash of the application.
* The URL to the repository where the project's source code is located.
*/
[REPOSITORY_URL_LABEL]?: string;

/**
* A hint as to which provider is being used to host the repository.
*/
[REPOSITORY_PROVIDER_LABEL]?: string;

/**
* The current version of the application.
*
* Should be set through the `AUTOMETRICS_BRANCH` environment variable, or by
* Should be set through the `AUTOMETRICS_VERSION` environment variable, or by
* explicitly specifying the `buildInfo` when calling `init()`.
*/
branch?: string;
[VERSION_LABEL]?: string;

/**
* The "clearmode" label of the `build_info` metric.
Expand All @@ -56,7 +90,15 @@ export type BuildInfo = {
*
* @internal
*/
const buildInfo: BuildInfo = {};
const buildInfo: BuildInfo = {
[AUTOMETRICS_VERSION_LABEL]: "1.0.0",
[BRANCH_LABEL]: "",
[COMMIT_LABEL]: "",
[REPOSITORY_PROVIDER_LABEL]: "",
[REPOSITORY_URL_LABEL]: "",
[VERSION_LABEL]: "",
clearmode: "",
};

let buildInfoGauge: UpDownCounter;

Expand All @@ -69,10 +111,22 @@ let buildInfoGauge: UpDownCounter;
export function recordBuildInfo(info: BuildInfo) {
debug("Recording build info");

buildInfo.version = info.version ?? "";
buildInfo.commit = info.commit ?? "";
buildInfo.branch = info.branch ?? "";
buildInfo.clearmode = info.clearmode ?? "";
for (const key of Object.keys(buildInfo)) {
const labelName = key as keyof BuildInfo;
const labelValue = info[labelName];
if (typeof labelValue === "string") {
(buildInfo[labelName] as string) = labelValue;
}
}

if (
info[REPOSITORY_URL_LABEL] &&
info[REPOSITORY_PROVIDER_LABEL] === undefined
) {
buildInfo[REPOSITORY_PROVIDER_LABEL] = detectRepositoryProvider(
info[REPOSITORY_URL_LABEL],
);
}

if (!buildInfoGauge) {
buildInfoGauge = getMeter().createUpDownCounter(BUILD_INFO_NAME, {
Expand All @@ -88,8 +142,10 @@ export function recordBuildInfo(info: BuildInfo) {
*/
export function createDefaultBuildInfo(): BuildInfo {
return {
version: getVersion(),
commit: getCommit(),
branch: getBranch(),
[VERSION_LABEL]: getVersion(),
[COMMIT_LABEL]: getCommit(),
[BRANCH_LABEL]: getBranch(),
[REPOSITORY_URL_LABEL]: getRepositoryUrl(),
[REPOSITORY_PROVIDER_LABEL]: getRepositoryProvider(),
};
}
21 changes: 21 additions & 0 deletions packages/autometrics/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
// Spec version
export const AUTOMETRICS_VERSION = "1.0.0";

// Metrics
export const COUNTER_NAME = "function.calls" as const;
export const HISTOGRAM_NAME = "function.calls.duration" as const;
export const GAUGE_NAME = "function.calls.concurrent" as const;
export const BUILD_INFO_NAME = "build_info" as const;

// Labels
export const AUTOMETRICS_VERSION_LABEL = "autometrics_version" as const;
export const BRANCH_LABEL = "branch" as const;
export const CALLER_FUNCTION_LABEL = "caller_function" as const;
export const CALLER_MODULE_LABEL = "caller_module" as const;
export const COMMIT_LABEL = "commit" as const;
export const FUNCTION_LABEL = "function" as const;
export const MODULE_LABEL = "module" as const;
export const OBJECTIVE_NAME_LABEL = "objective_name" as const;
export const OBJECTIVE_PERCENTILE_LABEL = "objective_percentile" as const;
export const OBJECTIVE_LATENCY_THRESHOLD_LABEL =
"objective_latency_threshold" as const;
export const REPOSITORY_URL_LABEL = "repository_url" as const;
export const REPOSITORY_PROVIDER_LABEL = "repository_provider" as const;
export const RESULT_LABEL = "result" as const;
export const SERVICE_NAME_LABEL = "service_name" as const;
export const VERSION_LABEL = "version" as const;

// Descriptions
export const COUNTER_DESCRIPTION =
"Autometrics counter for tracking function calls" as const;
Expand Down
50 changes: 50 additions & 0 deletions packages/autometrics/src/objectives.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import type { Attributes } from "npm:@opentelemetry/api@^1.6.0";

import {
OBJECTIVE_LATENCY_THRESHOLD_LABEL,
OBJECTIVE_NAME_LABEL,
OBJECTIVE_PERCENTILE_LABEL,
} from "./constants.ts";

/**
* This represents a Service-Level Objective (SLO) for a function or group of functions.
* The objective should be given a descriptive name and can represent
Expand Down Expand Up @@ -128,3 +136,45 @@ export enum ObjectiveLatency {
*/
Ms10000 = "10",
}

export function getObjectiveAttributes(objective: Objective | undefined): {
counterObjectiveAttributes: Attributes;
histogramObjectiveAttributes: Attributes;
} {
// NOTE - Gravel Gateway will reject two metrics of the same name if one of
// them has a subset of the attributes of the other. This means to be
// able to support functions that have objectives, as well as functions
// that do not have objectives, we need to default to setting the
// labels to empty strings.
const counterObjectiveAttributes: Attributes = {
[OBJECTIVE_NAME_LABEL]: "",
[OBJECTIVE_PERCENTILE_LABEL]: "",
};

const histogramObjectiveAttributes: Attributes = {
[OBJECTIVE_NAME_LABEL]: "",
[OBJECTIVE_LATENCY_THRESHOLD_LABEL]: "",
[OBJECTIVE_PERCENTILE_LABEL]: "",
};

if (objective) {
const { latency, name, successRate } = objective;

counterObjectiveAttributes[OBJECTIVE_NAME_LABEL] = name;
histogramObjectiveAttributes[OBJECTIVE_NAME_LABEL] = name;

if (latency) {
const [threshold, latencyPercentile] = latency;
histogramObjectiveAttributes[OBJECTIVE_LATENCY_THRESHOLD_LABEL] =
threshold;
histogramObjectiveAttributes[OBJECTIVE_PERCENTILE_LABEL] =
latencyPercentile;
}

if (successRate) {
counterObjectiveAttributes[OBJECTIVE_PERCENTILE_LABEL] = successRate;
}
}

return { counterObjectiveAttributes, histogramObjectiveAttributes };
}
26 changes: 26 additions & 0 deletions packages/autometrics/src/platform.deno.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AsyncLocalStorage } from "node:async_hooks";
import { getGitRepositoryUrl } from "./platformUtils.ts";

/**
* Returns the version of the application, based on environment variables.
Expand Down Expand Up @@ -38,6 +39,24 @@ export function getCwd(): string {
return Deno.cwd();
}

/**
* Returns the URL to the repository where the project's source code is located.
*
* @internal
*/
export function getRepositoryUrl(): string | undefined {
return Deno.env.get("AUTOMETRICS_REPOSITORY_URL") ?? detectRepositoryUrl();
}

/**
* Returns a hint as to which provider is being used to host the repository.
*
* @internal
*/
export function getRepositoryProvider(): string | undefined {
return Deno.env.get("AUTOMETRICS_REPOSITORY_PROVIDER");
}

/**
* Caller information we track across async function calls.
*
Expand All @@ -57,3 +76,10 @@ export type AsyncContext = { callerFunction?: string; callerModule?: string };
export function getALSInstance(): AsyncLocalStorage<AsyncContext> | undefined {
return new AsyncLocalStorage<AsyncContext>();
}

function detectRepositoryUrl(): string | undefined {
try {
const gitConfig = Deno.readFileSync(".git/config");
return getGitRepositoryUrl(gitConfig);
} catch {}
}
30 changes: 30 additions & 0 deletions packages/autometrics/src/platform.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// @ts-ignore statements in this file...

import { AsyncLocalStorage } from "node:async_hooks";
import { readFileSync } from "node:fs";

import { getGitRepositoryUrl } from "./platformUtils.ts";

/**
* Returns the version of the application, based on environment variables.
Expand Down Expand Up @@ -52,6 +55,26 @@ export function getCwd(): string {
return process.cwd();
}

/**
* Returns the URL to the repository where the project's source code is located.
*
* @internal
*/
export function getRepositoryUrl(): string | undefined {
// @ts-ignore
return process.env.AUTOMETRICS_REPOSITORY_URL ?? detectRepositoryUrl();
}

/**
* Returns a hint as to which provider is being used to host the repository.
*
* @internal
*/
export function getRepositoryProvider(): string | undefined {
// @ts-ignore
return process.env.AUTOMETRICS_REPOSITORY_PROVIDER;
}

/**
* Returns a new `AsyncLocalStorage` instance for storing caller information.
*
Expand All @@ -63,3 +86,10 @@ export function getALSInstance() {
callerModule?: string;
}>();
}

function detectRepositoryUrl(): string | undefined {
try {
const gitConfig = readFileSync(".git/config");
return getGitRepositoryUrl(gitConfig);
} catch {}
}
32 changes: 20 additions & 12 deletions packages/autometrics/src/platform.web.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
/**
* On web, environment variables don't exist, so we return an empty string.
* On web, environment variables don't exist, so we don't return anything.
*
* @internal
*/
export function getVersion(): string | undefined {
return "";
}
export function getVersion() {}

/**
* On web, environment variables don't exist, so we return an empty string.
* On web, environment variables don't exist, so we don't return anything.
*
* @internal
*/
export function getCommit(): string | undefined {
return "";
}
export function getCommit() {}

/**
* On web, environment variables don't exist, so we return an empty string.
* On web, environment variables don't exist, so we don't return anything.
*
* @internal
*/
export function getBranch(): string | undefined {
return "";
}
export function getBranch() {}

/**
* On web, there's no concept of a working directory, so we return an empty
Expand All @@ -35,6 +29,20 @@ export function getCwd(): string {
return "";
}

/**
* On web, environment variables don't exist, so we don't return anything.
*
* @internal
*/
export function getRepositoryUrl() {}

/**
* On web, environment variables don't exist, so we don't return anything.
*
* @internal
*/
export function getRepositoryProvider() {}

/**
* `AsyncLocalStorage` is not supported on web, so we don't return anything.
*
Expand Down
Loading

0 comments on commit 0e9eb37

Please sign in to comment.