This commit is contained in:
Mark Wolff 2019-04-15 16:02:14 -07:00
Коммит ae9b5bd2cf
18 изменённых файлов: 2060 добавлений и 0 удалений

4
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,4 @@
/build
/node_modules
.vscode
out

3
.gitmodules поставляемый Normal file
Просмотреть файл

@ -0,0 +1,3 @@
[submodule "external/googletest"]
path = external/googletest
url = https://github.com/google/googletest.git

6
.prettierrc Normal file
Просмотреть файл

@ -0,0 +1,6 @@
{
"semi": true,
"trailingComma": "none",
"singleQuote": false,
"tabWidth": 2
}

23
binding.gyp Normal file
Просмотреть файл

@ -0,0 +1,23 @@
{
"targets": [
{
"target_name": "native_metrics",
"sources": [
"src/GcProfiler.cc",
"src/LoopProfiler.cc",
"src/LoopProfiler.hh",
"src/Metric.hh",
"src/ResourceProfiler.cc",
"src/ResourceProfiler.hh",
"src/native_metrics.cc"
],
"defines": [
"NOMINMAX"
],
"include_dirs": [
"src",
"<!(node -e \"require('nan')\")"
]
}
]
}

1303
package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

42
package.json Normal file
Просмотреть файл

@ -0,0 +1,42 @@
{
"name": "applicationinsights-native-metrics",
"description": "Native APM agent for the Application Insights NodeJS SDK",
"version": "0.0.1",
"contributors": [
{
"name": "Application Insights Developer Support",
"email": "aidevsupport@microsoft.com"
},
{
"name": "Application Insights SDK Maintainers",
"email": "appinsightssdk@microsoft.com"
}
],
"main": "./out/main.js",
"scripts": {
"build:ts": "tsc --project ./tsconfig.json",
"build": "node-gyp configure && node-gyp rebuild",
"test": "mocha --recursive --expose-gc test/EndToEnd.js",
"test:native": "run-s test:renamebinding test:renametestbinding build test:runtests test:renameoriginalbinding",
"test:runtests": "node ./test_native/test.js",
"test:renamebinding": "rename-files ./ binding.gyp temp",
"test:renametestbinding": "cp test.gyp binding.gyp",
"test:renameoriginalbinding": "rename-files ./ temp binding.gyp",
"clean": "rimraf build/"
},
"gypfile": true,
"license": "MIT",
"dependencies": {
"nan": "^2.13.2"
},
"devDependencies": {
"@types/node": "^11.13.4",
"typescript": "^3.4.3",
"copyfiles": "^2.1.0",
"mocha": "^5.2.0",
"npm-run-all": "^4.1.5",
"rename-files": "0.0.2",
"rimraf": "^2.6.3",
"sinon": "^7.2.2"
}
}

38
src/GcProfiler.cc Normal file
Просмотреть файл

@ -0,0 +1,38 @@
#include "GcProfiler.hh"
namespace ai {
const double MS_TO_NS = 1.0e6;
GcProfiler* GcProfiler::_self = NULL;
NAN_METHOD(GcProfiler::New) {
if (_self != NULL) {
return Nan::ThrowError("GcProfiler already exists");
}
auto obj = new GcProfiler();
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
NAN_METHOD(GcProfiler::Data) {
Nan::HandleScope scope;
auto _this = GcProfiler::Unwrap<GcProfiler>(info.This());
auto resData = Nan::New<v8::Object>();
for (auto& metrics : _this->_metrics) {
Nan::Set(resData, Nan::New(metrics.first), metrics.second.toJSON());
metrics.second.reset();
}
info.GetReturnValue().Set(resData);
}
void GcProfiler::_gcDone(const v8::GCType gcType) {
auto durationHr = uv_hrtime() - _startTime;
_metrics[gcType] += (durationHr / MS_TO_NS);
}
} // namespace ai

80
src/GcProfiler.hh Normal file
Просмотреть файл

@ -0,0 +1,80 @@
#pragma once
#include <nan.h>
#include <map>
#include "Metric.hh"
namespace ai {
#define GC_PROFILER "GcProfiler"
class GcProfiler : public Nan::ObjectWrap {
public:
static NAN_METHOD(New);
static NAN_MODULE_INIT(Init) {
auto inst = Nan::New<v8::FunctionTemplate>(New);
inst->SetClassName(Nan::New(GC_PROFILER).ToLocalChecked());
inst->InstanceTemplate()->SetInternalFieldCount(1);
SetPrototypeMethod(inst, "data", Data);
SetPrototypeMethod(inst, "start", Start);
SetPrototypeMethod(inst, "stop", Stop);
constructor().Reset(Nan::GetFunction(inst).ToLocalChecked());
Nan::Set(target, Nan::New(GC_PROFILER).ToLocalChecked(),
Nan::GetFunction(inst).ToLocalChecked());
}
static NAN_METHOD(Data);
static NAN_METHOD(Start) { _start(); }
static NAN_METHOD(Stop) { _stop(); }
GcProfiler() {
_startTime = uv_hrtime();
_self = this;
}
~GcProfiler() {
_stop();
_self = NULL;
}
private:
static GcProfiler* _self;
static void _start() {
Nan::AddGCPrologueCallback(_aiGcPrologueCB);
Nan::AddGCEpilogueCallback(_aiGcEpilogueCB);
}
static void _stop() {
Nan::RemoveGCPrologueCallback(_aiGcPrologueCB);
Nan::RemoveGCEpilogueCallback(_aiGcEpilogueCB);
}
static NAN_GC_CALLBACK(_aiGcPrologueCB) {
// GC is starting, starting measurements
if (GcProfiler::_self) {
GcProfiler::_self->_gcStart();
}
}
static NAN_GC_CALLBACK(_aiGcEpilogueCB) {
// GC is finished, wrap up measurements
if (GcProfiler::_self) {
GcProfiler::_self->_gcDone(type);
}
}
static Nan::Persistent<v8::Function>& constructor() {
static Nan::Persistent<v8::Function> _newJsInst;
return _newJsInst;
}
void _gcStart() { _startTime = uv_hrtime(); }
void _gcDone(const v8::GCType gcType);
uint64_t _startTime; // result of uv_hrtime
std::map<v8::GCType, Metric<double>> _metrics;
};
#undef GC_PROFILER
} // namespace ai

56
src/LoopProfiler.cc Normal file
Просмотреть файл

@ -0,0 +1,56 @@
#include <uv.h>
#include <algorithm>
#include "LoopProfiler.hh"
namespace ai {
const uint64_t SEC_TO_MICRO = 1e6;
/**
* Get loop usage time in us
*/
uint64_t _getUsage() {
uv_rusage_t usage;
uv_getrusage(&usage);
const uint64_t usageUs =
(usage.ru_utime.tv_sec + usage.ru_stime.tv_sec * SEC_TO_MICRO) +
(usage.ru_utime.tv_usec + usage.ru_stime.tv_usec);
return usageUs;
}
/**
* Grab latest data from profiler
*/
NAN_METHOD(LoopProfiler::Data) {
Nan::HandleScope scope;
auto self = LoopProfiler::Unwrap<LoopProfiler>(info.This());
auto results = Nan::New<v8::Object>();
Nan::Set(results, Nan::New("loopUsage").ToLocalChecked(),
self->_tickUsage.toJSON());
info.GetReturnValue().Set(results);
self->_tickUsage.reset();
}
LoopProfiler::LoopProfiler() {
_thisTickUsage = _getUsage();
uv_check_init(uv_default_loop(), &_uvCheckHandle);
uv_unref((uv_handle_t*)&_uvCheckHandle);
_uvCheckHandle.data = (void*)this;
}
/**
* Start collecting event loop info -- on uv_check_start
*/
void LoopProfiler::_uvCheckStartCB(uv_check_t* handle) {
auto self = (LoopProfiler*)handle->data;
const auto usage = _getUsage();
self->_tickUsage += usage - self->_thisTickUsage;
self->_thisTickUsage = usage;
}
} // namespace ai

59
src/LoopProfiler.hh Normal file
Просмотреть файл

@ -0,0 +1,59 @@
#pragma once
#include <nan.h>
#include "Metric.hh"
namespace ai {
#define LOOP_PROFILER "LoopProfiler"
class LoopProfiler : public Nan::ObjectWrap {
public:
LoopProfiler();
static NAN_METHOD(New) {
auto obj = new LoopProfiler();
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
static NAN_MODULE_INIT(Init) {
auto inst = Nan::New<v8::FunctionTemplate>(New);
inst->SetClassName(Nan::New(LOOP_PROFILER).ToLocalChecked());
inst->InstanceTemplate()->SetInternalFieldCount(1);
SetPrototypeMethod(inst, "start", Start);
SetPrototypeMethod(inst, "stop", Stop);
SetPrototypeMethod(inst, "data", Data);
constructor().Reset(Nan::GetFunction(inst).ToLocalChecked());
Nan::Set(target, Nan::New(LOOP_PROFILER).ToLocalChecked(),
Nan::GetFunction(inst).ToLocalChecked());
}
static NAN_METHOD(Start) {
auto self = LoopProfiler::Unwrap<LoopProfiler>(info.This());
uv_check_start(&self->_uvCheckHandle, &LoopProfiler::_uvCheckStartCB);
}
static NAN_METHOD(Stop) {
auto self = LoopProfiler::Unwrap<LoopProfiler>(info.This());
uv_check_stop(&self->_uvCheckHandle);
}
static NAN_METHOD(Data);
private:
static Nan::Persistent<v8::Function>& constructor() {
static Nan::Persistent<v8::Function> _newJsInst;
return _newJsInst;
}
static void _uvCheckStartCB(uv_check_t* handle);
uv_check_t _uvCheckHandle;
uint64_t _thisTickUsage;
Metric<uint64_t> _tickUsage;
};
#undef LOOP_PROFILER
} // namespace ai

55
src/Metric.hh Normal file
Просмотреть файл

@ -0,0 +1,55 @@
#pragma once
#include <nan.h>
#include <algorithm>
namespace ai {
template <typename T>
class Metric {
public:
Metric() { _total = _min = _max = _count = (T)0; }
void reset() { _total = _min = _max = _count = (T)0; }
Metric& operator+=(const T& val) {
if (_count > 0) {
_min = std::min(_min, val);
_max = std::max(_max, val);
} else {
_min = _max = val;
}
_total += val;
_count++;
return *this;
}
v8::Local<v8::Object> toJSON() const {
auto resJson = Nan::New<v8::Object>();
Nan::Set(resJson, Nan::New("total").ToLocalChecked(),
Nan::New<v8::Number>(total()));
Nan::Set(resJson, Nan::New("min").ToLocalChecked(),
Nan::New<v8::Number>(min()));
Nan::Set(resJson, Nan::New("max").ToLocalChecked(),
Nan::New<v8::Number>(max()));
Nan::Set(resJson, Nan::New("count").ToLocalChecked(),
Nan::New<v8::Number>(count()));
return resJson;
}
const T& total() const { return _total; }
const T& min() const { return _min; }
const T& max() const { return _max; }
const T& count() const { return _count; }
private:
T _total;
T _min;
T _max;
T _count;
};
} // namespace ai

89
src/ResourceProfiler.cc Normal file
Просмотреть файл

@ -0,0 +1,89 @@
#pragma once
#include <nan.h>
#include <uv.h>
#include "ResourceProfiler.hh"
namespace ai {
NAN_METHOD(ResourceProfiler::Read) {
Nan::HandleScope scope;
auto self = ResourceProfiler::Unwrap<ResourceProfiler>(info.This());
auto results = Nan::New<v8::Object>();
self->_read();
Nan::Set(results, Nan::New("current").ToLocalChecked(),
self->_toJSON(self->_usagePrevious));
Nan::Set(results, Nan::New("diff").ToLocalChecked(),
self->_toJSON(self->_usageDiff));
info.GetReturnValue().Set(results);
}
void ResourceProfiler::_read() {
uv_rusage_t _thisUsage;
uv_getrusage(&_thisUsage);
_usageDiff.ru_utime.tv_sec = _thisUsage.ru_utime.tv_sec - _usagePrevious.ru_utime.tv_sec;
_usageDiff.ru_utime.tv_usec = _thisUsage.ru_utime.tv_usec - _usagePrevious.ru_utime.tv_usec;
_usageDiff.ru_stime.tv_sec = _thisUsage.ru_stime.tv_sec - _usagePrevious.ru_stime.tv_sec;
_usageDiff.ru_stime.tv_usec = _thisUsage.ru_stime.tv_usec - _usagePrevious.ru_stime.tv_usec;
_usageDiff.ru_msgsnd = _thisUsage.ru_msgsnd - _usagePrevious.ru_msgsnd;
_usageDiff.ru_msgrcv = _thisUsage.ru_msgrcv - _usagePrevious.ru_msgrcv;
_usageDiff.ru_nsignals = _thisUsage.ru_nsignals - _usagePrevious.ru_nsignals;
_usageDiff.ru_inblock = _thisUsage.ru_inblock - _usagePrevious.ru_inblock;
_usageDiff.ru_oublock = _thisUsage.ru_oublock - _usagePrevious.ru_oublock;
_usageDiff.ru_maxrss = _thisUsage.ru_maxrss - _usagePrevious.ru_maxrss;
_usageDiff.ru_ixrss = _thisUsage.ru_ixrss - _usagePrevious.ru_ixrss;
_usageDiff.ru_idrss = _thisUsage.ru_idrss - _usagePrevious.ru_idrss;
_usageDiff.ru_isrss = _thisUsage.ru_isrss - _usagePrevious.ru_isrss;
_usageDiff.ru_nvcsw = _thisUsage.ru_nvcsw - _usagePrevious.ru_nvcsw;
_usageDiff.ru_nivcsw = _thisUsage.ru_nivcsw - _usagePrevious.ru_nivcsw;
_usageDiff.ru_minflt = _thisUsage.ru_minflt - _usagePrevious.ru_minflt;
_usageDiff.ru_majflt = _thisUsage.ru_majflt - _usagePrevious.ru_majflt;
_usageDiff.ru_nswap = _thisUsage.ru_nswap - _usagePrevious.ru_nswap;
std::memcpy(&_usagePrevious, &_thisUsage, sizeof(uv_rusage_t));
}
v8::Local<v8::Object> ResourceProfiler::_toJSON(const uv_rusage_t& usage) {
auto resJson = Nan::New<v8::Object>();
double userTime = ((double)(usage.ru_utime.tv_sec * 1e3) +
(double)(usage.ru_utime.tv_usec / 1e3));
double sysTime = ((double)(usage.ru_stime.tv_sec * 1e3) +
(double)(usage.ru_stime.tv_usec / 1e3));
Nan::Set(resJson, Nan::New("ru_utime").ToLocalChecked(), Nan::New<v8::Number>((double)userTime));
Nan::Set(resJson, Nan::New("ru_stime").ToLocalChecked(), Nan::New<v8::Number>((double)sysTime));
Nan::Set(resJson, Nan::New("ru_msgsnd").ToLocalChecked(), Nan::New<v8::Number>((double)usage.ru_msgsnd));
Nan::Set(resJson, Nan::New("ru_msgrcv").ToLocalChecked(), Nan::New<v8::Number>((double)usage.ru_msgrcv));
Nan::Set(resJson, Nan::New("ru_nsignals").ToLocalChecked(), Nan::New<v8::Number>((double)usage.ru_nsignals));
Nan::Set(resJson, Nan::New("ru_inblock").ToLocalChecked(), Nan::New<v8::Number>((double)usage.ru_inblock));
Nan::Set(resJson, Nan::New("ru_oublock").ToLocalChecked(), Nan::New<v8::Number>((double)usage.ru_oublock));
Nan::Set(resJson, Nan::New("ru_maxrss").ToLocalChecked(), Nan::New<v8::Number>((double)usage.ru_maxrss));
Nan::Set(resJson, Nan::New("ru_ixrss").ToLocalChecked(), Nan::New<v8::Number>((double)usage.ru_ixrss));
Nan::Set(resJson, Nan::New("ru_idrss").ToLocalChecked(), Nan::New<v8::Number>((double)usage.ru_idrss));
Nan::Set(resJson, Nan::New("ru_isrss").ToLocalChecked(), Nan::New<v8::Number>((double)usage.ru_isrss));
Nan::Set(resJson, Nan::New("ru_nvcsw").ToLocalChecked(), Nan::New<v8::Number>((double)usage.ru_nvcsw));
Nan::Set(resJson, Nan::New("ru_nivcsw").ToLocalChecked(), Nan::New<v8::Number>((double)usage.ru_nivcsw));
Nan::Set(resJson, Nan::New("ru_minflt").ToLocalChecked(), Nan::New<v8::Number>((double)usage.ru_minflt));
Nan::Set(resJson, Nan::New("ru_majflt").ToLocalChecked(), Nan::New<v8::Number>((double)usage.ru_majflt));
Nan::Set(resJson, Nan::New("ru_nswap").ToLocalChecked(), Nan::New<v8::Number>((double)usage.ru_nswap));
return resJson;
}
} // namespace ai

46
src/ResourceProfiler.hh Normal file
Просмотреть файл

@ -0,0 +1,46 @@
#pragma once
#include <nan.h>
namespace ai {
#define RESOURCE_PROFILER "ResourceProfiler"
class ResourceProfiler : public Nan::ObjectWrap {
public:
static NAN_METHOD(New) {
auto obj = new ResourceProfiler();
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
static NAN_MODULE_INIT(Init) {
auto profiler = Nan::New<v8::FunctionTemplate>(New);
profiler->SetClassName(Nan::New(RESOURCE_PROFILER).ToLocalChecked());
profiler->InstanceTemplate()->SetInternalFieldCount(1);
SetPrototypeMethod(profiler, "read", Read);
constructor().Reset(Nan::GetFunction(profiler).ToLocalChecked());
Nan::Set(target, Nan::New(RESOURCE_PROFILER).ToLocalChecked(),
Nan::GetFunction(profiler).ToLocalChecked());
}
static NAN_METHOD(Read);
ResourceProfiler() { std::memset(&_usagePrevious, 0, sizeof(uv_rusage_t)); }
private:
static Nan::Persistent<v8::Function>& constructor() {
static Nan::Persistent<v8::Function> _newJsInst;
return _newJsInst;
}
void _read();
v8::Local<v8::Object> _toJSON(const uv_rusage_t& usage);
uv_rusage_t _usageDiff;
uv_rusage_t _usagePrevious;
};
#undef RESOURCE_PROFILER
} // namespace ai

118
src/main.ts Normal file
Просмотреть файл

@ -0,0 +1,118 @@
const events = require("events");
const natives = require("../build/Release/native_metrics");
type gcTypes =
| "Scavenge"
| "MarkSweepCompact"
| "IncrementalMarking"
| "ProcessWeakCallbacks"
| "All";
class NativeMetricEmitter extends events.EventEmitter {
private static GC_TYPES: { [key: number]: gcTypes } = {
1: "Scavenge",
2: "MarkSweepCompact",
3: "All", // For <= node4
4: "IncrementalMarking",
8: "ProcessWeakCallbacks",
15: "All" // For > node4
};
private static DEFAULT_INTERVAL = 15000;
private enabled: boolean;
private _handle: any;
private _loopProfiler: any;
private _gcProfiler: any;
private _resourceProfiler: any;
constructor(options: any) {
super(options);
options = options || { timeout: NativeMetricEmitter.DEFAULT_INTERVAL };
this.enabled = false;
this._handle = null;
this._loopProfiler = new natives.LoopProfiler();
this._gcProfiler = new natives.GcProfiler();
this._resourceProfiler = new natives.ResourceProfiler();
this.enable(true, options.timeout);
}
public enable(enable: boolean = true, pollInterval?: number) {
if (enable) {
this._start(pollInterval);
} else {
this._stop();
}
}
public getLoopData() {
return this._loopProfiler.data();
}
public getGCData() {
const gcMetrics: any[] = this._gcProfiler.data();
const results = Object.create(null);
for (let typeId in gcMetrics) {
if (gcMetrics.hasOwnProperty(typeId) && gcMetrics[typeId].count > 0) {
const typeName = NativeMetricEmitter.GC_TYPES[typeId];
results[typeName] = {
typeId: parseInt(typeId, 10),
type: typeName,
metrics: gcMetrics[typeId]
};
}
}
return results;
}
private _start(pollInterval?: number) {
if (this.enabled) {
return;
}
const interval = pollInterval || NativeMetricEmitter.DEFAULT_INTERVAL;
this._gcProfiler.start();
this._loopProfiler.start();
this._handle = setTimeout(this._emitUsage.bind(this, interval), interval);
this._handle.unref();
this.enabled = true;
}
private _stop() {
if (!this.enabled) {
return;
}
this._gcProfiler.stop();
this._loopProfiler.stop();
clearTimeout(this._handle);
this._handle = null;
this.enabled = false;
}
private _emitUsage(interval: number) {
if (this._resourceProfiler) {
this.emit("usage", this._resourceProfiler.read());
}
if (this.enabled) {
// Stop timer when disabled
this._handle = setTimeout(
this._emitUsage.bind(this, interval),
interval
);
this._handle.unref();
}
}
}
export = NativeMetricEmitter;

18
src/native_metrics.cc Normal file
Просмотреть файл

@ -0,0 +1,18 @@
#include <nan.h>
#include "GcProfiler.hh"
#include "LoopProfiler.hh"
#include "ResourceProfiler.hh"
namespace ai {
NAN_MODULE_INIT(Init) {
Nan::HandleScope scope;
GcProfiler::Init(target);
LoopProfiler::Init(target);
ResourceProfiler::Init(target);
}
NODE_MODULE(native_metrics, Init)
} // namespace ai

42
test.gyp Normal file
Просмотреть файл

@ -0,0 +1,42 @@
{
"targets": [
{
"target_name": "native_metrics",
"sources": [
"src/GcProfiler.cc",
"src/LoopProfiler.cc",
"src/LoopProfiler.hh",
"src/Metric.hh",
"src/ResourceProfiler.cc",
"src/ResourceProfiler.hh",
"src/native_metrics.cc"
],
"defines": [
"NOMINMAX"
],
"include_dirs": [
"src",
"<!(node -e \"require('nan')\")"
]
}, {
"target_name": "test_native",
"sources": [
"external/googletest/googletest/include/gtest/gtest.h",
"external/googletest/googletest/src/gtest-all.cc",
"test_native/main.cc",
"test_native/metrics_tests.cc"
],
"defines": [
"NOMINMAX"
],
"include_dirs": [
"external/googletest/googletest",
"external/googletest/googletest/include",
"src",
"test_native",
"<!(node -e \"require('nan')\")"
]
}
]
}

55
test/EndToEnd.js Normal file
Просмотреть файл

@ -0,0 +1,55 @@
"use strict";
const assert = require("assert");
var NativeMetricEmitter = require("../out/main");
const emitter = new NativeMetricEmitter();
describe("#EndToEnd", () => {
it("should collect manual global.gc events", () => {
if (emitter._gcProfiler) {
// Verify precondition
assert.deepEqual({}, emitter.getGCData());
// Act
global.gc(); // should trigger MarkSweepCompact
// Test postcondition
const gcMetrics = emitter.getGCData();
const gcTypes = Object.keys(gcMetrics);
assert.notDeepEqual({}, gcMetrics);
assert.equal(gcTypes.length, 1);
assert.equal(gcTypes[0], NativeMetricEmitter.GC_TYPES[2]);
assert.deepEqual({}, emitter.getGCData());
} else {
assert.ok(false, "gc not enabled");
}
});
it("should send resource usage information", done => {
emitter.enable(false);
emitter.enable(true, 10);
if (emitter._resourceProfiler) {
emitter.on("usage", usage => {
assert.ok(usage);
emitter.enable(false);
emitter.enable();
done();
});
} else {
assert.ok(false);
emitter.enable(false);
emitter.enable();
done();
}
});
it("should send event loop information", () => {
if (emitter._loopProfiler) {
assert.ok(emitter.getLoopData());
} else {
assert.ok(false);
}
});
});

23
tsconfig.json Normal file
Просмотреть файл

@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"declaration": true,
"noImplicitAny": true,
"strict": true,
"strictNullChecks": true,
"alwaysStrict": true,
"outDir": "./out",
"typeRoots": [
"./node_modules/@types"
]
},
"include": [
"src/*.ts",
],
"exclude": [
"node_modules"
]
}