Skip to content

Commit

Permalink
feat: Emit AppMap metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
dividedmind committed Sep 28, 2023
1 parent 11899be commit 8c6b0fd
Show file tree
Hide file tree
Showing 15 changed files with 130 additions and 22 deletions.
Binary file not shown.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,4 @@ development, not ready for production use, and the feature set is currently lim
- Only captures named `function`s and methods.
- CommonJS only.
- Only whole process recording and Jest per-test recordings.
- No metadata or classmap generation yet.
- No exception support.
1 change: 0 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@
- configurable output path
- configurable traced packages
- location paths relative to project root
- test recording
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"prettier": "^3.0.2",
"tmp": "^0.2.1",
"ts-node": "^10.9.1",
"type-fest": "^4.3.2",
"typescript": "^5.1.6"
},
"dependencies": {
Expand Down
16 changes: 13 additions & 3 deletions src/Recording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,26 @@ import { appMapDir } from "./config";
import { makeCallEvent, makeReturnEvent } from "./event";
import type { FunctionInfo } from "./registry";
import { makeClassMap } from "./classMap";
import { defaultMetadata } from "./metadata";

export default class Recording {
constructor(type: string, ...names: string[]) {
const dirs = [type, ...names];
constructor(type: AppMap.RecorderType, recorder: string, ...names: string[]) {
const dirs = [recorder, ...names];
const name = dirs.pop()!; // it must have at least one element
this.path = join(appMapDir, ...dirs, makeAppMapFilename(name));
this.stream = new AppMapStream(this.path);
this.metadata = {
...defaultMetadata,
recorder: { type, name: recorder },
name: names.join(" "),
};
}

private nextId = 1;
private functionsSeen = new Set<FunctionInfo>();
private stream: AppMapStream | undefined;
public readonly path;
public metadata: AppMap.Metadata;

functionCall(funInfo: FunctionInfo, thisArg: unknown, args: unknown[]): AppMap.FunctionCallEvent {
assert(this.stream);
Expand All @@ -43,7 +50,10 @@ export default class Recording {
}

finish(): boolean {
const written = this.stream?.close({ classMap: makeClassMap(this.functionsSeen.keys()) });
const written = this.stream?.close({
classMap: makeClassMap(this.functionsSeen.keys()),
metadata: this.metadata,
});
this.stream = undefined;
if (written) writtenAppMaps.push(this.path);
return !!written;
Expand Down
2 changes: 1 addition & 1 deletion src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { accessSync } from "node:fs";
import { resolve } from "node:path";

import { info } from "./message";
import { version } from "./version";
import { version } from "./metadata";

const registerPath = resolve(__dirname, "../dist/register.js");

Expand Down
4 changes: 2 additions & 2 deletions src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ function resolveParameters(args: unknown[], fun: FunctionInfo): Parameter[] {
);
}

function paramName(param: ESTree.Parameter): string | undefined {
switch (param.type) {
function paramName(param: ESTree.Parameter | undefined): string | undefined {
switch (param?.type) {
case "Identifier":
return param.name;
case "AssignmentPattern":
Expand Down
17 changes: 13 additions & 4 deletions src/hooks/jest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import type { ESTree } from "meriyah";
import type { Circus } from "@jest/types";

import { expressionFor, wrap } from ".";
import genericTranform from "../transform";
import { call_, identifier } from "../generate";
import { recording, start } from "../recorder";
import { info } from "../message";
import Recording from "../Recording";
import { recording, start } from "../recorder";
import genericTranform from "../transform";

export function shouldInstrument(url: URL): boolean {
return (
Expand Down Expand Up @@ -49,11 +50,14 @@ function isId(node: ESTree.Node | null, name: string) {
function eventHandler(event: Circus.Event) {
switch (event.name) {
case "test_fn_start":
start("jest", ...testNames(event.test));
start(createRecording(event.test));
break;
case "test_fn_failure":
recording.metadata.test_status = "failed";
return recording.finish();
case "test_fn_success":
recording.finish();
recording.metadata.test_status = "succeeded";
return recording.finish();
}
}

Expand All @@ -71,3 +75,8 @@ function testNames(test: Circus.TestEntry): string[] {
for (let block = test.parent; block.parent; block = block.parent) names.push(block.name);
return names.reverse();
}

function createRecording(test: Circus.TestEntry): Recording {
const recording = new Recording("tests", "jest", ...testNames(test));
return recording;
}
41 changes: 41 additions & 0 deletions src/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import process from "node:process";

import type { PackageJson } from "type-fest";

import type AppMap from "./AppMap";
import { root } from "./config";

// cannot use import because it's outside src
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkg = require("../package.json") as PackageJson;

export const version = pkg.version!;

export const defaultMetadata: Partial<AppMap.Metadata> & { client: AppMap.ClientMetadata } = {
client: {
name: pkg.name!,
version,
url: pkg.homepage!,
},
language: {
name: "ECMAScript",
engine: "Node.js",
version: process.version,
},
};

function readPkgUp(dir: string): PackageJson | undefined {
try {
return JSON.parse(readFileSync(join(dir, "package.json"), "utf-8")) as PackageJson;
} catch {
const parent = dirname(dir);
if (parent === dir) return;
else return readPkgUp(parent);
}
}

const targetPackage = readPkgUp(root);

if (targetPackage?.name) defaultMetadata.app = targetPackage.name;
6 changes: 3 additions & 3 deletions src/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { functions } from "./registry";
import Recording, { writtenAppMaps } from "./Recording";
import commonPathPrefix from "./util/commonPathPrefix";

export let recording: Recording = new Recording("process", new Date().toISOString());
export let recording: Recording = new Recording("process", "process", new Date().toISOString());

export function record<This, Return>(
this: This,
Expand Down Expand Up @@ -34,9 +34,9 @@ function isGlobal(obj: unknown): obj is typeof globalThis {
return typeof obj === "object" && obj !== null && "global" in obj && obj.global === obj;
}

export function start(type: string, ...names: string[]) {
export function start(newRecording: Recording) {
assert(!recording.running);
recording = new Recording(type, ...names);
recording = newRecording;
}

process.on("exit", () => {
Expand Down
5 changes: 0 additions & 5 deletions src/version.ts

This file was deleted.

19 changes: 19 additions & 0 deletions test/__snapshots__/jest.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,25 @@ exports[`mapping Jest tests 1`] = `
"thread_id": 0,
},
],
"metadata": {
"app": "jest-appmap-node-test",
"client": {
"name": "appmap-node",
"url": "https://github.com/getappmap/appmap-node",
"version": "0.0.1",
},
"language": {
"engine": "Node.js",
"name": "ECMAScript",
"version": "test node version",
},
"name": "sum sums numbers correctly",
"recorder": {
"name": "jest",
"type": "tests",
},
"test_status": "succeeded",
},
"version": "1.12",
},
}
Expand Down
18 changes: 18 additions & 0 deletions test/__snapshots__/simple.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@ exports[`mapping a simple script 1`] = `
"thread_id": 0,
},
],
"metadata": {
"app": "appmap-node",
"client": {
"name": "appmap-node",
"url": "https://github.com/getappmap/appmap-node",
"version": "0.0.1",
},
"language": {
"engine": "Node.js",
"name": "ECMAScript",
"version": "test node version",
},
"name": "test process recording",
"recorder": {
"name": "process",
"type": "process",
},
},
"version": "1.12",
}
`;
13 changes: 11 additions & 2 deletions test/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import assert from "node:assert";
import { spawnSync } from "node:child_process";
import { accessSync, readFileSync, rmSync } from "node:fs";
import { resolve } from "node:path";
import { cwd } from "node:process";

import caller from "caller";
import { globSync } from "fast-glob";
import assert from "node:assert";

import type AppMap from "../src/AppMap";

const binPath = resolve(__dirname, "../bin/appmap-node.js");

Expand Down Expand Up @@ -45,6 +47,8 @@ export function readAppmap(path?: string): AppMap {
assert(result.events instanceof Array);
result.events.forEach(fixEvent);
if ("classMap" in result && result.classMap instanceof Array) fixClassMap(result.classMap);
if ("metadata" in result && typeof result.metadata === "object" && result.metadata)
fixMetadata(result.metadata as AppMap.Metadata);

return result;
}
Expand Down Expand Up @@ -76,6 +80,11 @@ function fixClassMap(classMap: unknown[]) {
}
}

function fixMetadata(metadata: AppMap.Metadata) {
if (metadata.recorder.type === "process") metadata.name = "test process recording";
if (metadata.language) metadata.language.version = "test node version";
}

function ensureBuilt() {
const probePath = resolve(__dirname, "../dist/register.js");
try {
Expand All @@ -85,4 +94,4 @@ function ensureBuilt() {
}
}

ensureBuilt();
ensureBuilt();
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1503,6 +1503,7 @@ __metadata:
prettier: ^3.0.2
tmp: ^0.2.1
ts-node: ^10.9.1
type-fest: ^4.3.2
typescript: ^5.1.6
bin:
appmap-node: bin/appmap-node.js
Expand Down Expand Up @@ -4569,6 +4570,13 @@ __metadata:
languageName: node
linkType: hard

"type-fest@npm:^4.3.2":
version: 4.3.2
resolution: "type-fest@npm:4.3.2"
checksum: 8ba1b3d43e24888052d8c8859ae9b53124f8200c05808ec9247917ac3441612a7b36bf148a5b14150ef73ce7bad3cdca65ae923d37d2ae466c2b814d369fb975
languageName: node
linkType: hard

"typescript@npm:^5.1.6":
version: 5.1.6
resolution: "typescript@npm:5.1.6"
Expand Down

0 comments on commit 8c6b0fd

Please sign in to comment.