Skip to content

Commit 2f2e0e2

Browse files
committed
optional labels with sample rates
1 parent ec2e40d commit 2f2e0e2

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed

apps/kubernetes-provider/src/index.ts

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { PodCleaner } from "./podCleaner";
1818
import { TaskMonitor } from "./taskMonitor";
1919
import { UptimeHeartbeat } from "./uptimeHeartbeat";
2020
import { assertExhaustive } from "@trigger.dev/core";
21+
import { CustomLabelHelper } from "./labelHelper";
2122

2223
const RUNTIME_ENV = process.env.KUBERNETES_PORT ? "kubernetes" : "local";
2324
const NODE_NAME = process.env.NODE_NAME || "local";
@@ -73,6 +74,8 @@ class KubernetesTaskOperations implements TaskOperations {
7374
apps: k8s.AppsV1Api;
7475
};
7576

77+
#labelHelper = new CustomLabelHelper();
78+
7679
constructor(opts: { namespace?: string } = {}) {
7780
if (opts.namespace) {
7881
this.#namespace.metadata.name = opts.namespace;
@@ -165,6 +168,7 @@ class KubernetesTaskOperations implements TaskOperations {
165168
name: containerName,
166169
namespace: this.#namespace.metadata.name,
167170
labels: {
171+
...this.#labelHelper.getAdditionalLabels("create"),
168172
...this.#getSharedLabels(opts),
169173
app: "task-run",
170174
"app.kubernetes.io/part-of": "trigger-worker",
@@ -226,6 +230,7 @@ class KubernetesTaskOperations implements TaskOperations {
226230
name: `${this.#getRunContainerName(opts.runId)}-${opts.checkpointId.slice(-8)}`,
227231
namespace: this.#namespace.metadata.name,
228232
labels: {
233+
...this.#labelHelper.getAdditionalLabels("restore"),
229234
...this.#getSharedLabels(opts),
230235
app: "task-run",
231236
"app.kubernetes.io/part-of": "trigger-worker",
+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { assertExhaustive } from "@trigger.dev/core";
2+
3+
const CREATE_LABEL_ENV_VAR_PREFIX = "DEPLOYMENT_LABEL_";
4+
const RESTORE_LABEL_ENV_VAR_PREFIX = "RESTORE_LABEL_";
5+
const LABEL_SAMPLE_RATE_POSTFIX = "_SAMPLE_RATE";
6+
7+
type OperationType = "create" | "restore";
8+
9+
type CustomLabel = {
10+
key: string;
11+
value: string;
12+
sampleRate: number;
13+
};
14+
15+
export class CustomLabelHelper {
16+
// Labels and sample rates are defined in environment variables so only need to be computed once
17+
private createLabels?: CustomLabel[];
18+
private restoreLabels?: CustomLabel[];
19+
20+
private getLabelPrefix(type: OperationType) {
21+
const prefix = type === "create" ? CREATE_LABEL_ENV_VAR_PREFIX : RESTORE_LABEL_ENV_VAR_PREFIX;
22+
return prefix.toLowerCase();
23+
}
24+
25+
private getLabelSampleRatePostfix() {
26+
return LABEL_SAMPLE_RATE_POSTFIX.toLowerCase();
27+
}
28+
29+
// Can only range from 0 to 1
30+
private fractionFromPercent(percent: number) {
31+
return Math.min(1, Math.max(0, percent / 100));
32+
}
33+
34+
private isLabelSampleRateEnvVar(key: string) {
35+
return key.toLowerCase().endsWith(this.getLabelSampleRatePostfix());
36+
}
37+
38+
private isLabelEnvVar(type: OperationType, key: string) {
39+
const prefix = this.getLabelPrefix(type);
40+
return key.toLowerCase().startsWith(prefix) && !this.isLabelSampleRateEnvVar(key);
41+
}
42+
43+
private getSampleRateEnvVarKey(type: OperationType, envKey: string) {
44+
return `${envKey.toLowerCase()}${this.getLabelSampleRatePostfix()}`;
45+
}
46+
47+
private getLabelNameFromEnvVarKey(type: OperationType, key: string) {
48+
return key
49+
.slice(this.getLabelPrefix(type).length)
50+
.toLowerCase()
51+
.replace(/___/g, ".")
52+
.replace(/__/g, "/")
53+
.replace(/_/g, "-");
54+
}
55+
56+
private getCaseInsensitiveEnvValue(key: string) {
57+
for (const [envKey, value] of Object.entries(process.env)) {
58+
if (envKey.toLowerCase() === key.toLowerCase()) {
59+
return value;
60+
}
61+
}
62+
}
63+
64+
/** Returns the sample rate for a given label as fraction of 100 */
65+
private getSampleRateFromEnvVarKey(type: OperationType, envKey: string) {
66+
// Apply default: always sample
67+
const DEFAULT_SAMPLE_RATE_PERCENT = 100;
68+
const defaultSampleRateFraction = this.fractionFromPercent(DEFAULT_SAMPLE_RATE_PERCENT);
69+
70+
const value = this.getCaseInsensitiveEnvValue(this.getSampleRateEnvVarKey(type, envKey));
71+
72+
if (!value) {
73+
return defaultSampleRateFraction;
74+
}
75+
76+
const sampleRatePercent = parseFloat(value || String(DEFAULT_SAMPLE_RATE_PERCENT));
77+
78+
if (isNaN(sampleRatePercent)) {
79+
return defaultSampleRateFraction;
80+
}
81+
82+
const fractionalSampleRate = this.fractionFromPercent(sampleRatePercent);
83+
84+
return fractionalSampleRate;
85+
}
86+
87+
private getCustomLabels(type: OperationType): CustomLabel[] {
88+
switch (type) {
89+
case "create":
90+
if (this.createLabels) {
91+
return this.createLabels;
92+
}
93+
break;
94+
case "restore":
95+
if (this.restoreLabels) {
96+
return this.restoreLabels;
97+
}
98+
break;
99+
default:
100+
assertExhaustive(type);
101+
}
102+
103+
const customLabels: CustomLabel[] = [];
104+
105+
for (const [envKey, value] of Object.entries(process.env)) {
106+
const key = envKey.toLowerCase();
107+
108+
// Only process env vars that start with the expected prefix
109+
if (!this.isLabelEnvVar(type, key)) {
110+
continue;
111+
}
112+
113+
// Skip sample rates - deal with them separately
114+
if (this.isLabelSampleRateEnvVar(key)) {
115+
continue;
116+
}
117+
118+
const labelName = this.getLabelNameFromEnvVarKey(type, key);
119+
const sampleRate = this.getSampleRateFromEnvVarKey(type, key);
120+
121+
console.log({ key, value, sampleRate, labelName });
122+
123+
const label = {
124+
key: labelName,
125+
value: value || "",
126+
sampleRate,
127+
} satisfies CustomLabel;
128+
129+
customLabels.push(label);
130+
}
131+
132+
console.log({
133+
type,
134+
customLabels,
135+
});
136+
137+
return customLabels;
138+
}
139+
140+
getAdditionalLabels(type: OperationType): Record<string, string> {
141+
const labels = this.getCustomLabels(type);
142+
143+
const additionalLabels: Record<string, string> = {};
144+
145+
for (const { key, value, sampleRate } of labels) {
146+
// Always apply label if sample rate is 1
147+
if (sampleRate === 1) {
148+
additionalLabels[key] = value;
149+
continue;
150+
}
151+
152+
if (Math.random() <= sampleRate) {
153+
additionalLabels[key] = value;
154+
continue;
155+
}
156+
}
157+
158+
return additionalLabels;
159+
}
160+
}

0 commit comments

Comments
 (0)