Skip to content

Profiling misattributes usage in Node-API native addons #59976

@kjvalencik

Description

@kjvalencik

Version

v24.7.0

Platform

macOS

Subsystem

Node-API

What steps will reproduce the bug?

Profile a native addon with node --inspect performance tab (or --prof). All calls to the native addon will be attributed to the last function that's defined.

#include <stddef.h>
#include <stdio.h>

#define SIZE_MAX ((size_t)-1)
#define NAPI_AUTO_LENGTH SIZE_MAX

typedef int napi_status;

typedef void* napi_value;
typedef void* napi_env;
typedef void* napi_callback_info;

typedef napi_value (*napi_callback)(napi_env, napi_callback_info);

napi_status napi_create_function(
	napi_env env,
	const char* utf8name,
	size_t length,
	napi_callback cb,
	void* data,
	napi_value* result
);

napi_status napi_set_named_property(
	napi_env env,
	napi_value object,
	const char* utf8Name,
	napi_value value
);

napi_value busy_loop(napi_env _env, napi_callback_info _info) {
	printf("busy loop called\n");

	for (int i = 0; i < 1e9; i++) {}

	return 0;
}

napi_value noop(napi_env _env, napi_callback_info _info) {
	printf("noop called\n");
	return 0;
}

napi_value napi_register_module_v1(napi_env env, napi_value m) {
	napi_value result;
	napi_status status;

	status = napi_create_function(env, "busyLoop", NAPI_AUTO_LENGTH, busy_loop, NULL, &result);
	if (status != 0) {
		printf("failed to create busyLoop\n");
		return m;
	}

	status = napi_set_named_property(env, m, "busyLoop", result);
	if (status != 0) {
		printf("failed to export busyLoop\n");
		return m;
	}

	status = napi_create_function(env, "noop", NAPI_AUTO_LENGTH, noop, NULL, &result);
	if (status != 0) {
		printf("failed to create noop\n");
		return m;
	}

	status = napi_set_named_property(env, m, "noop", result);
	if (status != 0) {
		printf("failed to export noop\n");
		return m;
	}

    return m;
}
"use strict";

const { busyLoop } = require("./index.node");

const delay = n => new Promise(r => setTimeout(r, n));

async function run() {
    await delay(1000);

    for (let i = 0; i < 10; i++) {
        busyLoop();
    }
}

run();
node --inspect-brk index.js

How often does it reproduce? Is there a required condition?

Only with native addons built with Node-API. However, it happens regardless of language (e.g., plain C or Rust/Neon).

What is the expected behavior? Why is that the expected behavior?

The correct method is attributed.

What do you see instead?

Image Image

Additional information

A minimal reproduction can be found in this repo. It exports two functions (busyLoop and noop) and all time is attributed to noop instead of busyLoop, despite it never being called.

https://github.com/kjvalencik/node-addon-profile

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions