Skip to content

Commit 7a45b37

Browse files
committed
An example ts project
1 parent c75c282 commit 7a45b37

File tree

8 files changed

+417
-124
lines changed

8 files changed

+417
-124
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
node_modules/
22
dist/
3+
dist-tsc/
34
coverage/
45
*.tsbuildinfo
56
.DS_Store

examples/sample-ts/README.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Sample TypeScript Server for eBPF Profiling
2+
3+
A TypeScript server designed for profiling with eBPF tools, featuring continuous computation workloads and source map support for symbol resolution.
4+
5+
## Features
6+
7+
- **Continuous CPU-intensive workloads**: Fibonacci calculations, prime number searches, and matrix multiplication
8+
- **Source map generation**: Enables TypeScript symbol resolution in profiling tools
9+
- **Health endpoint**: `/health` for Kubernetes probes
10+
- **Graceful shutdown**: Handles SIGINT/SIGTERM signals
11+
- **Docker containerized**: Multi-stage build with minimal runtime image
12+
- **Kubernetes ready**: Deployment manifests included
13+
14+
## Environment Variables
15+
16+
Both build variants use the same environment variables:
17+
18+
```bash
19+
export POLARSIGNALS_PROJECT_ID=<your-project-id>
20+
export POLARSIGNALS_TOKEN=<your-token>
21+
export POLARSIGNALS_SERVER_URL=grpc.polarsignals.com:443 # optional, this is the default
22+
```
23+
24+
## Build Variants
25+
26+
### esbuild (plugin-based)
27+
28+
Uses `@polarsignals/sourcemap-esbuild-plugin` — debug ID injection and upload happen automatically as part of the esbuild build.
29+
30+
```bash
31+
pnpm run build:esbuild
32+
```
33+
34+
### tsc (CLI-based)
35+
36+
Uses plain `tsc` for compilation, then `@polarsignals/sourcemap-cli` to inject debug IDs and upload source maps.
37+
38+
```bash
39+
pnpm run build:tsc
40+
```
41+
42+
## Running
43+
44+
```bash
45+
# Run esbuild output
46+
pnpm start
47+
48+
# Run tsc output
49+
pnpm start:tsc
50+
```
51+
52+
## Docker Build & Run
53+
54+
```bash
55+
# Build the Docker image
56+
docker build -t sample-ts-server:latest .
57+
58+
# Run locally
59+
docker run -p 3000:3000 sample-ts-server:latest
60+
61+
# Test health endpoint
62+
curl http://localhost:3000/health
63+
```
64+
65+
## Kubernetes Deployment
66+
67+
### Prerequisites
68+
- Colima cluster running
69+
- Docker image available in cluster
70+
71+
```bash
72+
# Build and load image into Colima
73+
docker build -t sample-ts-server:latest .
74+
docker save sample-ts-server:latest | colima ssh -- sudo ctr -n k8s.io image import -
75+
76+
# Deploy to Kubernetes
77+
kubectl apply -f k8s-deployment.yaml
78+
79+
# Check deployment status
80+
kubectl get pods -n sample-ts
81+
82+
# Check assigned NodePort and access service
83+
kubectl get svc -n sample-ts
84+
curl http://localhost:<nodeport>/health # Use the assigned port from above command
85+
86+
# View logs
87+
kubectl logs -n sample-ts -l app=sample-ts-server -f
88+
```
89+
90+
## Profiling Setup
91+
92+
This server is designed to work with eBPF profilers like Parca Agent. The workload patterns include:
93+
94+
1. **Fibonacci calculations** (recursive, stack-heavy)
95+
2. **Prime number searches** (CPU-intensive loops)
96+
3. **Matrix multiplication** (memory allocation and computation)
97+
4. **Intermittent sleeps** (varying activity patterns)
98+
99+
### Source Map Configuration
100+
101+
- TypeScript compiled with `sourceMap: true`
102+
- Source files included in container at `/app/src`
103+
- JavaScript with source maps at `/app/dist`
104+
- Source maps reference original TypeScript files
105+
106+
## Cleanup
107+
108+
```bash
109+
# Remove Kubernetes resources
110+
kubectl delete -f k8s-deployment.yaml
111+
112+
# Remove Docker image
113+
docker rmi sample-ts-server:latest
114+
```
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// esbuild.config.js - Production build configuration
2+
import esbuild from "esbuild";
3+
import { debugIdPlugin } from "@polarsignals/sourcemap-esbuild-plugin";
4+
5+
const buildConfig = {
6+
entryPoints: ["src/server.ts"],
7+
bundle: true,
8+
minify: true,
9+
sourcemap: true,
10+
platform: "node",
11+
target: "node24",
12+
format: "esm", // Output ES modules
13+
outfile: "dist/server.js",
14+
external: [], // Bundle all dependencies for standalone deployment
15+
16+
// Source map configuration
17+
sourceRoot: "", // Relative source root
18+
sourcesContent: false, // Don't embed source content in map
19+
20+
// Advanced minification options
21+
treeShaking: true,
22+
minifyWhitespace: true,
23+
minifyIdentifiers: true,
24+
minifySyntax: true,
25+
26+
// Keep function names for better profiling
27+
keepNames: false, // Set to true if you want to preserve function names
28+
29+
// More aggressive minification
30+
mangleQuoted: false, // Don't preserve quoted properties
31+
32+
// Define environment
33+
define: {
34+
"process.env.NODE_ENV": '"production"',
35+
},
36+
37+
// Banner for debugging info
38+
banner: {
39+
js: "// Built with esbuild - minified for production",
40+
},
41+
42+
// Plugins
43+
plugins: [
44+
debugIdPlugin({
45+
verbose: true,
46+
projectID: process.env.POLARSIGNALS_PROJECT_ID,
47+
debuginfoServerUrl: process.env.POLARSIGNALS_SERVER_URL,
48+
token: process.env.POLARSIGNALS_TOKEN,
49+
}),
50+
],
51+
};
52+
53+
// Export configurations
54+
export { buildConfig };
55+
56+
async function build() {
57+
const result = await esbuild.build(buildConfig);
58+
if (result.errors.length > 0) {
59+
process.exit(1);
60+
}
61+
}
62+
63+
// Run if called directly
64+
if (import.meta.url === `file://${process.argv[1]}`) {
65+
build();
66+
}

examples/sample-ts/package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "@polarsignals/sample-ts-server",
3+
"version": "1.0.0",
4+
"private": true,
5+
"description": "TypeScript server for eBPF profiling with source map support",
6+
"main": "dist/server.js",
7+
"type": "module",
8+
"scripts": {
9+
"build:esbuild": "node esbuild.config.js",
10+
"build:tsc": "rm -rf dist-tsc && tsc --project tsconfig.tsc.json && sourcemap-upload dist-tsc --verbose",
11+
"start": "node dist/server.js",
12+
"start:tsc": "node dist-tsc/server.js",
13+
"clean": "rm -rf dist dist-tsc"
14+
},
15+
"keywords": [
16+
"typescript",
17+
"profiling",
18+
"ebpf",
19+
"sourcemap"
20+
],
21+
"author": "Polarsignals",
22+
"license": "MIT",
23+
"devDependencies": {
24+
"@polarsignals/sourcemap-cli": "workspace:^",
25+
"@polarsignals/sourcemap-esbuild-plugin": "workspace:^",
26+
"@types/node": "^20.10.0",
27+
"esbuild": "^0.19.0",
28+
"typescript": "^5.3.3"
29+
},
30+
"engines": {
31+
"node": ">=18.0.0"
32+
}
33+
}

examples/sample-ts/src/server.ts

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import * as http from 'http';
2+
3+
const PORT = process.env.PORT || 3000;
4+
5+
// Global state
6+
let server: http.Server;
7+
let isRunning = true;
8+
9+
// Request handler
10+
function handleRequest(req: http.IncomingMessage, res: http.ServerResponse) {
11+
if (req.url === '/health') {
12+
res.writeHead(200, {'Content-Type': 'application/json'});
13+
res.end(JSON.stringify({status: 'healthy', timestamp: Date.now()}));
14+
} else {
15+
res.writeHead(404);
16+
res.end('Not Found!!!!!!');
17+
}
18+
}
19+
20+
// Computation functions
21+
function computeFibonacci(n: number): number {
22+
if (n <= 1) return n;
23+
return computeFibonacci(n - 1) + computeFibonacci(n - 2);
24+
}
25+
26+
function checkPrime(num: number): boolean {
27+
if (num <= 1) return false;
28+
if (num <= 3) return true;
29+
if (num % 2 === 0 || num % 3 === 0) return false;
30+
31+
for (let i = 5; i * i <= num; i += 6) {
32+
if (num % i === 0 || num % (i + 2) === 0) return false;
33+
}
34+
return true;
35+
}
36+
37+
function findPrimesInRange(start: number, end: number): number[] {
38+
const primes: number[] = [];
39+
for (let i = start; i <= end; i++) {
40+
if (checkPrime(i)) {
41+
primes.push(i);
42+
}
43+
}
44+
return primes;
45+
}
46+
47+
function multiplyMatrices(size: number): number[][] {
48+
const a = Array(size)
49+
.fill(0)
50+
.map(() =>
51+
Array(size)
52+
.fill(0)
53+
.map(() => Math.random())
54+
);
55+
const b = Array(size)
56+
.fill(0)
57+
.map(() =>
58+
Array(size)
59+
.fill(0)
60+
.map(() => Math.random())
61+
);
62+
const result = Array(size)
63+
.fill(0)
64+
.map(() => Array(size).fill(0));
65+
66+
for (let i = 0; i < size; i++) {
67+
for (let j = 0; j < size; j++) {
68+
for (let k = 0; k < size; k++) {
69+
result[i][j] += a[i][k] * b[k][j];
70+
}
71+
}
72+
}
73+
return result;
74+
}
75+
76+
async function sleep(ms: number): Promise<void> {
77+
return new Promise(resolve => setTimeout(resolve, ms));
78+
}
79+
80+
async function runComputationLoop(): Promise<void> {
81+
let iteration = 0;
82+
83+
while (isRunning) {
84+
iteration++;
85+
console.log(`Starting computation iteration ${iteration} at ${new Date().toISOString()}`);
86+
87+
// Fibonacci computation (CPU intensive)
88+
const fibStart = Date.now();
89+
const fibResult = computeFibonacci(30 + (iteration % 5));
90+
console.log(
91+
`Fibonacci(${30 + (iteration % 5)}) = ${fibResult} (took ${Date.now() - fibStart}ms)`
92+
);
93+
94+
// Short sleep
95+
await sleep(100);
96+
97+
// Prime number search (CPU intensive)
98+
const primeStart = Date.now();
99+
const rangeStart = 1000 + iteration * 100;
100+
const rangeEnd = rangeStart + 500;
101+
const primes = findPrimesInRange(rangeStart, rangeEnd);
102+
console.log(
103+
`Found ${primes.length} primes in range ${rangeStart}-${rangeEnd} (took ${
104+
Date.now() - primeStart
105+
}ms)`
106+
);
107+
108+
// Short sleep
109+
await sleep(150);
110+
111+
// Matrix multiplication (memory and CPU intensive)
112+
const matrixStart = Date.now();
113+
const matrixSize = 50 + (iteration % 20);
114+
multiplyMatrices(matrixSize);
115+
console.log(
116+
`Matrix multiplication ${matrixSize}x${matrixSize} completed (took ${
117+
Date.now() - matrixStart
118+
}ms)`
119+
);
120+
121+
// Longer sleep between iterations
122+
await sleep(500);
123+
}
124+
}
125+
126+
function startServer(): Promise<void> {
127+
return new Promise(resolve => {
128+
server = http.createServer(handleRequest);
129+
server.listen(PORT, () => {
130+
console.log(`TypeScript computation server listening on port ${PORT}`);
131+
console.log(`Health endpoint available at http://localhost:${PORT}/health`);
132+
resolve();
133+
});
134+
});
135+
}
136+
137+
function stopServer(): void {
138+
isRunning = false;
139+
if (server) {
140+
server.close();
141+
}
142+
}
143+
144+
// Handle graceful shutdown
145+
process.on('SIGINT', () => {
146+
console.log('Received SIGINT, shutting down gracefully...');
147+
stopServer();
148+
process.exit(0);
149+
});
150+
151+
process.on('SIGTERM', () => {
152+
console.log('Received SIGTERM, shutting down gracefully...');
153+
stopServer();
154+
process.exit(0);
155+
});
156+
157+
// Start server and computation loop
158+
async function main() {
159+
try {
160+
await startServer();
161+
console.log('Starting continuous computation loop...');
162+
await runComputationLoop();
163+
} catch (error) {
164+
console.error('Server error:', error);
165+
process.exit(1);
166+
}
167+
}
168+
169+
main();

0 commit comments

Comments
 (0)