1import * as HostProcess from "@t3tools/shared/HostProcess";
2import * as Context from "effect/Context";
3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";
5import * as Layer from "effect/Layer";
6import * as Option from "effect/Option";
7import * as Path from "effect/Path";
8import * as Schema from "effect/Schema";
9
10import * as ServerConfig from "../config.ts";
11
12export class ResourceMonitorBinaryUnsupported extends Schema.TaggedError<ResourceMonitorBinaryUnsupported>()(
13 "ResourceMonitorBinaryUnsupported",
14 {
15 platform: Schema.String,
16 architecture: Schema.String,
17 },
18) {
19 override get message(): string {
20 return `Resource monitoring is unsupported on ${this.platform}/${this.architecture}.`;
21 }
22}
23
24export class ResourceMonitorBinaryNotFound extends Schema.TaggedError<ResourceMonitorBinaryNotFound>()(
25 "ResourceMonitorBinaryNotFound",
26 {
27 platform: Schema.String,
28 architecture: Schema.String,
29 candidates: Schema.Array(Schema.String),
30 },
31) {
32 override get message(): string {
33 return `Resource monitor binary was not found for ${this.platform}/${this.architecture}.`;
34 }
35}
36
37export class ResourceMonitorBinaryNotExecutable extends Schema.TaggedError<ResourceMonitorBinaryNotExecutable>()(
38 "ResourceMonitorBinaryNotExecutable",
39 {
40 path: Schema.String,
41 mode: Schema.Number,
42 },
43) {
44 override get message(): string {
45 return `Resource monitor binary at '${this.path}' is not executable.`;
46 }
47}
48
49export type ResourceMonitorBinaryError =
50 | ResourceMonitorBinaryUnsupported
51 | ResourceMonitorBinaryNotFound
52 | ResourceMonitorBinaryNotExecutable;
53
54export class ResourceMonitorBinary extends Context.Service<
55 ResourceMonitorBinary,
56 {
57 readonly resolve: Effect.Effect<string, ResourceMonitorBinaryError>;
58 }
59>()("t3/resourceTelemetry/ResourceMonitorBinary") {}
60
61function binaryName(platform: NodeJS.Platform): string {
62 return platform === "win32" ? "t3-resource-monitor.exe" : "t3-resource-monitor";
63}
64
65export type ResourceMonitorLinuxLibc = "gnu" | "musl";
66
67function detectResourceMonitorLinuxLibc(): ResourceMonitorLinuxLibc {
68 try {
69 const report = process.report?.getReport() as
70 | {
71 readonly header?: {
72 readonly glibcVersionRuntime?: unknown;
73 };
74 }
75 | undefined;
76 return typeof report?.header?.glibcVersionRuntime === "string" ? "gnu" : "musl";
77 } catch {
78 return "musl";
79 }
80}
81
82export const ResourceMonitorHostLinuxLibc = Context.Reference<ResourceMonitorLinuxLibc>(
83 "t3/resourceTelemetry/ResourceMonitorHostLinuxLibc",
84 {
85 defaultValue: detectResourceMonitorLinuxLibc,
86 },
87);
88
89function resourceMonitorPlatformKey(
90 platform: NodeJS.Platform,
91 architecture: NodeJS.Architecture,
92): string | undefined {
93 if (
94 (platform !== "darwin" && platform !== "linux" && platform !== "win32") ||
95 (architecture !== "arm64" && architecture !== "x64")
96 ) {
97 return undefined;
98 }
99 return `${platform}-${architecture}`;
100}
101
102function resourceMonitorRustTarget(
103 platform: NodeJS.Platform,
104 architecture: NodeJS.Architecture,
105 linuxLibc?: ResourceMonitorLinuxLibc,
106): string | undefined {
107 if (platform === "darwin") {
108 return architecture === "arm64"
109 ? "aarch64-apple-darwin"
110 : architecture === "x64"
111 ? "x86_64-apple-darwin"
112 : undefined;
113 }
114 if (platform === "linux") {
115 if (linuxLibc !== "gnu") {
116 return undefined;
117 }
118 return architecture === "arm64"
119 ? "aarch64-unknown-linux-gnu"
120 : architecture === "x64"
121 ? "x86_64-unknown-linux-gnu"
122 : undefined;
123 }
124 if (platform === "win32") {
125 return architecture === "arm64"
126 ? "aarch64-pc-windows-msvc"
127 : architecture === "x64"
128 ? "x86_64-pc-windows-msvc"
129 : undefined;
130 }
131 return undefined;
132}
133
134export const make = Effect.fn("resourceTelemetry.resourceMonitorBinary.make")(function* () {
135 const config = yield* ServerConfig.ServerConfig;
136 const fileSystem = yield* FileSystem.FileSystem;
137 const path = yield* Path.Path;
138 const platform = yield* HostProcess.Platform;
139 const architecture = yield* HostProcess.Architecture;
140 const environment = yield* HostProcess.Environment;
141 const linuxLibc = platform === "linux" ? yield* ResourceMonitorHostLinuxLibc : undefined;
142 const executableName = binaryName(platform);
143 const platformKey = resourceMonitorPlatformKey(platform, architecture);
144 const rustTarget = resourceMonitorRustTarget(platform, architecture, linuxLibc);
145 const overrideCandidates = [
146 environment.T3CODE_RESOURCE_MONITOR_PATH,
147 config.resourceMonitorPath,
148 ].filter((candidate): candidate is string => Boolean(candidate));
149 const bundledCandidates =
150 platformKey === undefined || rustTarget === undefined
151 ? []
152 : [
153 path.resolve(import.meta.dirname, "resource-monitor", platformKey, executableName),
154 path.resolve(import.meta.dirname, "resource-monitor", executableName),
155 path.resolve(import.meta.dirname, "../resource-monitor", executableName),
156 path.resolve(
157 import.meta.dirname,
158 "../../../../native/resource-monitor/target",
159 rustTarget,
160 "release",
161 executableName,
162 ),
163 path.resolve(
164 import.meta.dirname,
165 "../../../native/resource-monitor/target",
166 rustTarget,
167 "release",
168 executableName,
169 ),
170 path.resolve(
171 import.meta.dirname,
172 "../../../../native/resource-monitor/target/release",
173 executableName,
174 ),
175 path.resolve(
176 import.meta.dirname,
177 "../../../../native/resource-monitor/target/debug",
178 executableName,
179 ),
180 ];
181 if (overrideCandidates.length === 0 && bundledCandidates.length === 0) {
182 return ResourceMonitorBinary.of({
183 resolve: Effect.fail(
184 new ResourceMonitorBinaryUnsupported({
185 platform,
186 architecture,
187 }),
188 ),
189 });
190 }
191
192 const candidates = [...overrideCandidates, ...bundledCandidates];
193
194 const resolve: ResourceMonitorBinary["Service"]["resolve"] = Effect.gen(function* () {
195 for (const candidate of candidates) {
196 const exists = yield* fileSystem.exists(candidate).pipe(Effect.orElseSucceed(() => false));
197 if (!exists) continue;
198
199 const stat = yield* fileSystem.stat(candidate).pipe(Effect.option);
200 if (Option.isSome(stat)) {
201 // Directories are executable-looking on win32, where the exec-bit test
202 // below is skipped, and only fail later at spawn.
203 const unusable =
204 stat.value.type === "Directory" ||
205 (platform !== "win32" && (stat.value.mode & 0o111) === 0);
206 if (unusable) {
207 return yield* new ResourceMonitorBinaryNotExecutable({
208 path: candidate,
209 mode: stat.value.mode,
210 });
211 }
212 }
213
214 return candidate;
215 }
216
217 return yield* new ResourceMonitorBinaryNotFound({
218 platform,
219 architecture,
220 candidates,
221 });
222 });
223
224 return ResourceMonitorBinary.of({ resolve });
225});
226
227export const layer = Layer.effect(ResourceMonitorBinary, make());