initial commit
This commit is contained in:
Коммит
ae9b5bd2cf
|
@ -0,0 +1,4 @@
|
|||
/build
|
||||
/node_modules
|
||||
.vscode
|
||||
out
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "external/googletest"]
|
||||
path = external/googletest
|
||||
url = https://github.com/google/googletest.git
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"semi": true,
|
||||
"trailingComma": "none",
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2
|
||||
}
|
|
@ -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')\")"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
|
@ -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
|
|
@ -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')\")"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -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"
|
||||
]
|
||||
}
|
Загрузка…
Ссылка в новой задаче