Skip to content
Source
Sign inSign up

ResourceMonitorBinary.ts

193 lines6.2 KB

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 }
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 }
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 }
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";
64​
65function resourceMonitorPlatformKey(
66 platform: NodeJS.Platform,
67 architecture: NodeJS.Architecture,
68): string | undefined {
69 if (
70 (platform !== "darwin" && platform !== "linux" && platform !== "win32") ||
71 (architecture !== "arm64" && architecture !== "x64")
72 ) {
73 return undefined;
74 }
75 return `${platform}-${architecture}`;
77​
78function resourceMonitorRustTarget(
79 platform: NodeJS.Platform,
80 architecture: NodeJS.Architecture,
81 linuxLibc?: typeof HostProcess.LinuxLibc.Service,
82): string | undefined {
83 if (platform === "darwin") {
84 return architecture === "arm64"
85 ? "aarch64-apple-darwin"
86 : architecture === "x64"
87 ? "x86_64-apple-darwin"
88 : undefined;
89 }
90 if (platform === "linux") {
91 if (linuxLibc !== "gnu") {
92 return undefined;
93 }
94 return architecture === "arm64"
95 ? "aarch64-unknown-linux-gnu"
96 : architecture === "x64"
97 ? "x86_64-unknown-linux-gnu"
98 : undefined;
99 }
100 if (platform === "win32") {
101 return architecture === "arm64"
102 ? "aarch64-pc-windows-msvc"
103 : architecture === "x64"
104 ? "x86_64-pc-windows-msvc"
105 : undefined;
106 }
107 return undefined;
109​
110export const make = Effect.fn("resourceTelemetry.resourceMonitorBinary.make")(function* () {
111 const config = yield* ServerConfig.ServerConfig;
112 const fileSystem = yield* FileSystem.FileSystem;
113 const path = yield* Path.Path;
114 const platform = yield* HostProcess.Platform;
115 const architecture = yield* HostProcess.Architecture;
116 const environment = yield* HostProcess.Environment;
117 const linuxLibc = platform === "linux" ? yield* HostProcess.LinuxLibc : undefined;
118 const executableName = binaryName(platform);
119 const platformKey = resourceMonitorPlatformKey(platform, architecture);
120 const rustTarget = resourceMonitorRustTarget(platform, architecture, linuxLibc);
121 const overrideCandidates = [
122 environment.T3CODE_RESOURCE_MONITOR_PATH,
123 config.resourceMonitorPath,
124 ].filter((candidate): candidate is string => Boolean(candidate));
125 const cargoOutputDirectories =
126 rustTarget === undefined
127 ? []
128 : [
129 path.join("target", rustTarget, "release"),
130 path.join("target", "release"),
131 path.join("target", "debug"),
132 ];
133 const sourceCandidates = [
134 path.resolve(import.meta.dirname, "../../../../native/resource-monitor"),
135 path.resolve(import.meta.dirname, "../../../native/resource-monitor"),
136 ].flatMap((crateDirectory) =>
137 cargoOutputDirectories.map((directory) => path.join(crateDirectory, directory, executableName)),
138 );
139 const bundledCandidates =
140 platformKey === undefined || rustTarget === undefined
141 ? []
142 : [
143 path.resolve(import.meta.dirname, "resource-monitor", platformKey, executableName),
144 path.resolve(import.meta.dirname, "resource-monitor", executableName),
145 path.resolve(import.meta.dirname, "../resource-monitor", executableName),
146 ...sourceCandidates,
147 ];
148 if (overrideCandidates.length === 0 && bundledCandidates.length === 0) {
149 return ResourceMonitorBinary.of({
150 resolve: Effect.fail(
151 new ResourceMonitorBinaryUnsupported({
152 platform,
153 architecture,
154 }),
155 ),
156 });
157 }
158​
159 const candidates = [...overrideCandidates, ...bundledCandidates];
160​
161 const resolve: ResourceMonitorBinary["Service"]["resolve"] = Effect.gen(function* () {
162 for (const candidate of candidates) {
163 const exists = yield* fileSystem.exists(candidate).pipe(Effect.orElseSucceed(() => false));
164 if (!exists) continue;
165​
166 const stat = yield* fileSystem.stat(candidate).pipe(Effect.option);
167 if (Option.isSome(stat)) {
168 // Windows skips exec-bit checks, so directories would otherwise pass until spawn.
169 const unusable =
170 stat.value.type === "Directory" ||
171 (platform !== "win32" && (stat.value.mode & 0o111) === 0);
172 if (unusable) {
173 return yield* new ResourceMonitorBinaryNotExecutable({
174 path: candidate,
175 mode: stat.value.mode,
176 });
177 }
178 }
179​
180 return candidate;
181 }
182​
183 return yield* new ResourceMonitorBinaryNotFound({
184 platform,
185 architecture,
186 candidates,
187 });
188 });
189​
190 return ResourceMonitorBinary.of({ resolve });
191});
192​
193export const layer = Layer.effect(ResourceMonitorBinary, make());