diff --git a/.npmignore b/.npmignore index 457994f..2bef7cd 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,5 @@ -.lock-wscript *.node +.lock-wscript *~ .#* *# diff --git a/README.md b/README.md index 30dd7ea..d6ef77e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,10 @@ This extension allows you to create native DTrace providers for your Node.js applications. That is, to create providers and probes which expose information specific to your application, rather than -information about the node runtime. +information about the node runtime. The extension also supports Win32 +and uses ETW WinAPI to implement similar functionality provided by +DTrace on non-win platforms. That is why using this addon under Win32 +is different from the other platforms. You could use this to expose high-level information about the inner workings of your application, or to create a specific context in which @@ -19,13 +22,79 @@ need not run as root. $ npm install dtrace-provider +*Windows-only:* + +The ETW code uses some C++11 features and requires at least VS2012 for it. +To specify the version, set the appropriate environment variable. + + GYP_MSVS_VERSION=2012 + +It must be set before invoking npm install or creating a project. + +Creating the Visual Studio project files is done using node-gyp. This is a +node add-on for creating the appropriate builds for your installation. +To create a Visual Studio solution: + + node-gyp configure + +The Windows implementation supports Node.js 0.11 as well as previous releases. +To build the addon for a release version below 0.11, uncomment + + #define BUILD_PRE011_COMPATIBILITY + +in + + src/etw_win/v8_compatibility.h + +## PLATFORM-SPECIFIC INFORMATION + +On the system API level, providers are created and controlled differently +on Windows and the other supported platforms. +For the platforms supporting DTrace the user needs to supply the names for +the provider and the module to create the provider, while Windows requires +a GUID which is also used to control the tracing session. + +To handle this difference, there are two ways to create the provider. +Both options can be used for all of the supported platforms. + +*Option 1 (compatibility way):* + + var d = require('dtrace-provider'); + var dtp = d.createDTraceProvider("provider", "module"); + +The createDTraceProvider function takes the name of the provider as the +first argument; the second argument is an optional module name. Both +arguments are strings. This is a compatibility function left to allow +the existing node apps using this extension to be able to work without +modifying their code. + +*Option 2 (cross-platform way):* + + var d = require('dtrace-provider'); + var provider = d.createTraceProvider({provider_name: 'provider', module_name: 'module', guid: '5A391F32-A079-42E1-97B1-8A45295AA1FD'}); + +The createTraceProvider function takes an object with the above-written +properties. The user can be specific about what information they want to +supply to the function. In the situation when the information required +by the platform has not been passed in, it will be generated from what +has been given. + +That is why the extension also exposes two helper functions: guidFromNames +and namesFromGuid exposed on Windows and the other platforms respectively. These +functions use the same algorithms that are used by createTraceProvider and +can provide the neccessary information to control the session. + +See the following files for the sample code. + + test_crossplatform/guid-from-names.js + test_dtrace/names-from-guid.js + ## EXAMPLE Here's a simple example of creating a provider: var d = require('dtrace-provider'); - - var dtp = d.createDTraceProvider("nodeapp"); + var dtp = d.createTraceProvider({provider_name: 'nodeapp'}); var p1 = dtp.addProbe("probe1", "int", "int"); var p2 = dtp.addProbe("probe2", "char *"); dtp.enable(); @@ -48,11 +117,23 @@ or via the probe objects themselves: return ["hello, dtrace via probe", "foo"]; }); -This example creates a provider called "nodeapp", and adds two +The example above creates a provider called "nodeapp", and adds two probes. It then enables the provider, at which point the provider becomes visible to DTrace. -The probes are then fired, which produces this output: +The probes are then fired. + +## GENERAL INFORMATION + +Available on all of the supported platforms argument types are "int", for +integer numeric values, "char *" for strings, and "json" for objects rendered into +JSON strings. Arguments typed as "json" will be created as "char *" probes in +the provider, but objects passed to these probe arguments will be automatically +serialized to JSON before being passed forward to the system API. + +## DTRACE INFORMATION + +Firing the probes from the example above produces this output: $ sudo dtrace -Z -n 'nodeapp*:::probe1{ trace(arg0); trace(arg1) }' \ -n 'nodeapp*:::probe2{ trace(copyinstr(arg0)); }' @@ -67,14 +148,8 @@ enabled. This means you can do more expensive work to gather arguments. The maximum number of arguments supported is 32. -Available argument types are "int", for integer numeric values, -"char *" for strings, and "json" for objects rendered into JSON strings. - -Arguments typed as "json" will be created as "char *" probes in -DTrace, but objects passed to these probe arguments will be -automatically serialized to JSON before being passed to DTrace. This -feature is best used in conjunction with the json() D subroutine, but -is available whether or not the platform supports it. +The JSON serialization feature is best used in conjunction with the json() +D subroutine, but is available whether or not the platform supports it. # create a json probe: @@ -90,6 +165,135 @@ is available whether or not the platform supports it. dtrace: description 'nodeapp$target:::j1' matched 0 probes CPU ID FUNCTION:NAME 0 68712 j1:j1 bar + + +## ETW INFORMATION + +**Controlling the session** + +Before the probes are fired the user must start a tracing session. It +can be done with session controllers like logman. After they have been +fired, the session must be stopped. Starting/stopping the session +requires the administrator privileges. + +To start tracing + + logman start mytrace -p {GUID} -o mytrace.etl -ets + +To stop tracing + + logman stop mytrace -ets + + +The guid may not always be known. In this case, the guidFromNames +function must be used. The following example demonstrates it. + +There is an application where the provider has been created +without specifying the GUID: + + var extension = require('dtrace-provider'); + var provider = extension.createTraceProvider({provider_name: 'nodeapp'}); + //var provider = extension.createDTraceProvider('nodeapp'); //An alternative way to create the provider. + +We need to call guidFromNames before running this application. +The code snippet below demonstates how it can be done: + + var extension = require('dtrace-provider'); + var generated_guid = extension.guidFromNames('nodeapp'); + console.log('generated guid = ' + generated_guid); + +This code will print the GUID generated from the specified name. +The output will look like: + + generated guid = FAC03859-DB22-3EC7-B1B5-4E6E99A4B546 + +Then the session can be started with + + logman start mytrace -p {FAC03859-DB22-3EC7-B1B5-4E6E99A4B546} -o mytrace.etl -ets + +After the session has been started, the original application +can be executed and all the probes fired will be recorded by ETW. + +The guidFromNames function allows the optional module name to be passed +as well. For example: + + var extension = require('dtrace-provider'); + var generated_guid = extension.guidFromNames('nodeapp', 'my_module'); + var provider = extension.createTraceProvider({provider_name: 'nodeapp', module_name: 'my_module'}); + //var provider = extension.createDTraceProvider('nodeapp', 'my_module'); //An alternative way to create the provider. + +This works for as long as there is a one-to-one match for the names. +It is guaranteed that the GUID will always be same for same strings, i.e. +the generation algorithm is deterministic. + +**Consuming events** + +After the session has been stopped, an etl file will appear. It can be +converted to a human-readable XML with tracerpt. The output will +contain the information about the probes and raw binary data. + +To convert trace output to xml file (data will show names and values) + + tracerpt mytrace.etl + +To convert trace output to csv file (data will show names and values) + + tracerpt mytrace.etl -of CSV + +The other way of consuming events is using tools that allow a manifest +file to be supplied. The manifest contains information about the data +types of the probes added and allows the raw binary data to be parsed +correctly. The manifest is updated in runtime whenever a new probe is +created with the addProbe function and it remains valid after each update. + +Some of these tools: + + PerfView - http://www.microsoft.com/en-us/download/details.aspx?id=28567 + LinqPad with Tx - http://tx.codeplex.com + +For the example script the PerfView output will look like: + + ThreadID="6,772" FormattedMessage="Node.js probe" Argument1="1" Argument2="2" + ThreadID="6,772" FormattedMessage="Node.js probe" Argument1="hello, dtrace via provider" + + +The manifest file is unique for each provider. It is created in the +process working directory with its name given according to the pattern: + + provider_GUID.man + +For the 'nodeapp' provider name it will look like: + + provider_FAC03859-DB22-3EC7-B1B5-4E6E99A4B546.man + +**Windows-only features** + +The extension provides a variety of types under Windows in addition to +the types supported on both platforms. +Those types are: +int8, int16, int32, int64, uin8, uint16, uint32, uint64 and wchar_t * + +Where 'int32' is an alias for 'int'. + +When the extension is used under Windows, the user can be specific +about the ETW descriptor for the events (probes). + + var provider = extension.createTraceProvider({provider_name: 'my_provider', module_name: 'my_module', guid: '5A391F32-A079-42E1-97B1-8A45295AA1FD'}); + var desc = [10, 0, 0, 0, 0, 0, 0]; + var probe = provider.addProbe('event1', desc1, 'char *', 'int16'); + +The array matches the EVENT_DESCRIPTOR structure. + + http://msdn.microsoft.com/en-us/library/windows/desktop/aa363754%28v=vs.85%29.aspx + +When the probes are created without the descriptor array, the event id +is incremented for each new probe; the other fields are zero-initialized. + +The descriptor array will be ignored on all of the supported platforms, +except Windows. Passing the types supported only under Windows +on the other platforms will result in their fallback to the dtrace types. +For example, "uint16" will be handled as "int" and "wchar_t *" will be +handled as "char *". ## PLATFORM SUPPORT @@ -98,15 +302,19 @@ Mac OS X and Solaris-like systems such as Illumos or SmartOS. As more platform support is added to libusdt, those platforms will be supported by this module. See libusdt's status at: - https://github.com/chrisa/libusdt#readme + https://github.com/chrisa/libusdt#readme FreeBSD is supported in principle but is restricted to only 4 working arguments per probe. -Platforms not supporting DTrace (notably, Linux and Windows) may +The Win 32/64 implementation doesn't use libusdt and relies directly +on WinAPI. + +Platforms not supporting DTrace (notably, Linux) may install this module without building libusdt, with a stub no-op -implementation provided for compatibility. This allows cross-platform -npm modules to embed probes and include a dependency on this module. +implementation provided for compatibility. The Windows implementation +doesn't require libusdt either. This allows cross-platform npm modules to +embed probes and include a dependency on this module. GNU Make is required to build libusdt; the build scripts will look for gmake in PATH first, and then for make. @@ -123,18 +331,18 @@ unless probes are placed in particularly hot code paths. The source is available at: - https://github.com/chrisa/node-dtrace-provider. + https://github.com/chrisa/node-dtrace-provider. For issues, please use the Github issue tracker linked to the repository. Github pull requests are very welcome. ## RUNNING THE TESTS - $ npm install - $ sudo ./node_modules/.bin/tap --tap test/*.test.js + $ npm install + $ sudo ./node_modules/.bin/tap --tap test_dtrace/*.test.js ## OTHER IMPLEMENTATIONS This node extension is derived from the ruby-dtrace gem, via the Perl module Devel::DTrace::Provider, both of which provide the same -functionality to those languages. +functionality to those languages. \ No newline at end of file diff --git a/binding.gyp b/binding.gyp index 1cb6b4d..4d25e2e 100644 --- a/binding.gyp +++ b/binding.gyp @@ -7,14 +7,14 @@ 'targets': [ { - 'target_name': 'DTraceProviderBindings', + 'target_name': 'TraceProviderBindings', 'sources': [ - 'dtrace_provider.cc', - 'dtrace_probe.cc', - 'dtrace_argument.cc' + 'src/dtrace/dtrace_provider.cc', + 'src/dtrace/dtrace_probe.cc', + 'src/dtrace/dtrace_argument.cc' ], 'include_dirs': [ - 'libusdt' + 'libusdt' ], 'dependencies': [ 'libusdt' @@ -30,25 +30,45 @@ 'inputs': [''], 'outputs': [''], 'action_name': 'build_libusdt', - 'action': [ + 'action': [ 'sh', 'libusdt-build.sh' - ] + ] }] } ] + }], + ['OS=="win"', { + + # If we are on Windows, attempt + # to build the ETW provider extension. + + 'targets': [ + { + 'target_name': 'TraceProviderBindings', + 'sources': [ + 'src/etw_win/etw_provider.cc', + 'src/etw_win/v8_provider.cc', + 'src/etw_win/v8_probe.cc', + 'src/etw_win/manifest_builder.cc' + ], + 'link_settings': { + 'libraries': [ "rpcrt4.lib" ] + } + } + ] }, - # If we are not on the Mac or Solaris, DTrace is unavailable. + # If we are not on the Mac, Solaris or Windows, tracing is unavailable. # This target is necessary because GYP requires at least one # target to exist. { 'targets': [ { - 'target_name': 'DTraceProviderStub', + 'target_name': 'TraceProviderStub', 'type': 'none' } ] }] ] -} +} \ No newline at end of file diff --git a/dtrace-provider.js b/dtrace-provider.js index 4816236..54eea38 100644 --- a/dtrace-provider.js +++ b/dtrace-provider.js @@ -1,39 +1,70 @@ -var DTraceProvider; +var TraceProviderCreatorFunction; +var NamesFromGuidFunction; +var GuidFromNamesFunction; +var platform = process.platform; +var is_platform_supported = false; -function DTraceProviderStub() {} -DTraceProviderStub.prototype.addProbe = function() { +function TraceProviderStub() { } +TraceProviderStub.prototype.addProbe = function() { return { 'fire': function() { } }; }; -DTraceProviderStub.prototype.enable = function() {}; -DTraceProviderStub.prototype.fire = function() {}; + +TraceProviderStub.prototype.removeProbe = function () { }; +TraceProviderStub.prototype.enable = function () { }; +TraceProviderStub.prototype.disable = function () { }; +TraceProviderStub.prototype.fire = function () { }; +NamesFromGuidFunction = function () { return ['not implemented on this platform', 'not implemented on this platform'] }; +GuidFromNamesFunction = function () { return 'not implemented on this platform' }; +TraceProviderCreatorFunction = TraceProviderStub; + +if (process.platform == 'darwin' || + process.platform == 'solaris' || + process.platform == 'freebsd' || + process.platform == 'win32') { + is_platform_supported = true; +} var builds = ['Release', 'default', 'Debug']; for (var i in builds) { - try { - var binding = require('./build/' + builds[i] + '/DTraceProviderBindings'); - DTraceProvider = binding.DTraceProvider; - break; - } catch (e) { - // if the platform looks like it _should_ have DTrace - // available, log a failure to load the bindings. - if (process.platform == 'darwin' || - process.platform == 'solaris' || - process.platform == 'freebsd') { - console.log(e); - } - } + try { + var binding = require('./build/' + builds[i] + '/TraceProviderBindings'); + TraceProviderCreatorFunction = binding.createTraceProvider; + + //If we are on win32 - expose the guidFromNames function to see what guid will be generated for the ETW provider if the user didn't specify it. + if(platform == 'win32') { + GuidFromNamesFunction = binding.guidFromNames; + } else { + NamesFromGuidFunction = binding.namesFromGuid; + } + + break; + } catch (e) { + //If the platform looks like it _should_ support the extension, + //log a failure to load the bindings. + if (is_platform_supported) { + console.log(e); + } + } } -if (!DTraceProvider) { - DTraceProvider = DTraceProviderStub; -} +//Expose the universal function for provider creation. +exports.TraceProvider = TraceProviderCreatorFunction; +exports.namesFromGuid = NamesFromGuidFunction; +exports.guidFromNames = GuidFromNamesFunction; +exports.createTraceProvider = function (object) { + return new TraceProviderCreatorFunction(object); +}; -exports.DTraceProvider = DTraceProvider; -exports.createDTraceProvider = function(name, module) { - if (arguments.length == 2) - return (new DTraceProvider(name, module)); - return (new DTraceProvider(name)); +//We must also expose the dtrace-only creator function to support existing code. +exports.createDTraceProvider = function (name, module) { + if (arguments.length == 2) { + return (new TraceProviderCreatorFunction(name, module)); + } + else { + return (new TraceProviderCreatorFunction(name)); + } }; + diff --git a/dtrace_provider.cc b/dtrace_provider.cc deleted file mode 100644 index ec338f2..0000000 --- a/dtrace_provider.cc +++ /dev/null @@ -1,194 +0,0 @@ -#include "dtrace_provider.h" -#include - -#include -#include - -namespace node { - - using namespace v8; - - DTraceProvider::DTraceProvider() : ObjectWrap() { - provider = NULL; - } - - DTraceProvider::~DTraceProvider() { - usdt_provider_disable(provider); - usdt_provider_free(provider); - } - - Persistent DTraceProvider::constructor_template; - - void DTraceProvider::Initialize(Handle target) { - HandleScope scope; - - Local t = FunctionTemplate::New(DTraceProvider::New); - constructor_template = Persistent::New(t); - constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - constructor_template->SetClassName(String::NewSymbol("DTraceProvider")); - - NODE_SET_PROTOTYPE_METHOD(constructor_template, "addProbe", DTraceProvider::AddProbe); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "removeProbe", DTraceProvider::RemoveProbe); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "enable", DTraceProvider::Enable); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "disable", DTraceProvider::Disable); - NODE_SET_PROTOTYPE_METHOD(constructor_template, "fire", DTraceProvider::Fire); - - target->Set(String::NewSymbol("DTraceProvider"), constructor_template->GetFunction()); - - DTraceProbe::Initialize(target); - } - - Handle DTraceProvider::New(const Arguments& args) { - HandleScope scope; - DTraceProvider *p = new DTraceProvider(); - char module[128]; - - p->Wrap(args.This()); - - if (args.Length() < 1 || !args[0]->IsString()) { - return ThrowException(Exception::Error(String::New( - "Must give provider name as argument"))); - } - - String::AsciiValue name(args[0]->ToString()); - - if (args.Length() == 2) { - if (!args[1]->IsString()) { - return ThrowException(Exception::Error(String::New( - "Must give module name as argument"))); - } - - String::AsciiValue mod(args[1]->ToString()); - (void) snprintf(module, sizeof (module), "%s", *mod); - } else if (args.Length() == 1) { - // If no module name is provided, develop a synthetic module name based - // on our address - (void) snprintf(module, sizeof (module), "mod-%p", p); - } else { - return ThrowException(Exception::Error(String::New( - "Expected only provider name and module as arguments"))); - } - - if ((p->provider = usdt_create_provider(*name, module)) == NULL) { - return ThrowException(Exception::Error(String::New( - "usdt_create_provider failed"))); - } - - return args.This(); - } - - Handle DTraceProvider::AddProbe(const Arguments& args) { - HandleScope scope; - const char *types[USDT_ARG_MAX]; - - Handle obj = args.Holder(); - DTraceProvider *provider = ObjectWrap::Unwrap(obj); - - // create a DTraceProbe object - Handle klass = DTraceProbe::constructor_template->GetFunction(); - Handle pd = Local::New(klass->NewInstance()); - - // store in provider object - DTraceProbe *probe = ObjectWrap::Unwrap(pd->ToObject()); - obj->Set(args[0]->ToString(), pd); - - // add probe to provider - for (int i = 0; i < USDT_ARG_MAX; i++) { - if (i < args.Length() - 1) { - String::AsciiValue type(args[i + 1]->ToString()); - - if (strncmp("json", *type, 4) == 0) - probe->arguments[i] = new DTraceJsonArgument(); - else if (strncmp("char *", *type, 6) == 0) - probe->arguments[i] = new DTraceStringArgument(); - else if (strncmp("int", *type, 3) == 0) - probe->arguments[i] = new DTraceIntegerArgument(); - else - probe->arguments[i] = new DTraceStringArgument(); - - types[i] = strdup(probe->arguments[i]->Type()); - probe->argc++; - } - } - - String::AsciiValue name(args[0]->ToString()); - probe->probedef = usdt_create_probe(*name, *name, probe->argc, types); - usdt_provider_add_probe(provider->provider, probe->probedef); - - for (size_t i = 0; i < probe->argc; i++) { - free((char *)types[i]); - } - - return pd; - } - - Handle DTraceProvider::RemoveProbe(const Arguments& args) { - HandleScope scope; - - Handle provider_obj = args.Holder(); - DTraceProvider *provider = ObjectWrap::Unwrap(provider_obj); - - Handle probe_obj = Local::Cast(args[0]); - DTraceProbe *probe = ObjectWrap::Unwrap(probe_obj); - - Handle name = String::New(probe->probedef->name); - provider_obj->Delete(name); - - if (usdt_provider_remove_probe(provider->provider, probe->probedef) != 0) - return ThrowException(Exception::Error(String::New(usdt_errstr(provider->provider)))); - - return True(); - } - - Handle DTraceProvider::Enable(const Arguments& args) { - HandleScope scope; - DTraceProvider *provider = ObjectWrap::Unwrap(args.Holder()); - - if (usdt_provider_enable(provider->provider) != 0) - return ThrowException(Exception::Error(String::New(usdt_errstr(provider->provider)))); - - return Undefined(); - } - - Handle DTraceProvider::Disable(const Arguments& args) { - HandleScope scope; - DTraceProvider *provider = ObjectWrap::Unwrap(args.Holder()); - - if (usdt_provider_disable(provider->provider) != 0) - return ThrowException(Exception::Error(String::New(usdt_errstr(provider->provider)))); - - return Undefined(); - } - - Handle DTraceProvider::Fire(const Arguments& args) { - HandleScope scope; - - if (!args[0]->IsString()) { - return ThrowException(Exception::Error(String::New( - "Must give probe name as first argument"))); - } - - if (!args[1]->IsFunction()) { - return ThrowException(Exception::Error(String::New( - "Must give probe value callback as second argument"))); - } - - Handle provider = args.Holder(); - Handle probe = Local::Cast(provider->Get(args[0])); - - DTraceProbe *p = ObjectWrap::Unwrap(probe); - if (p == NULL) - return Undefined(); - - p->_fire(args[1]); - - return True(); - } - - extern "C" void - init(Handle target) { - DTraceProvider::Initialize(target); - } - - NODE_MODULE(DTraceProviderBindings, init) -} // namespace node diff --git a/etw_tools/auto_testing.bat b/etw_tools/auto_testing.bat new file mode 100644 index 0000000..69b199b --- /dev/null +++ b/etw_tools/auto_testing.bat @@ -0,0 +1,27 @@ +for /f "tokens=*" %%i in (tests.txt) do ( +call start_trace +node ..\%testsDir%\%%i.js>console_output.txt +call stop_trace +mkdir ..\test_results\%folderName%\%%i\ +xcopy summary.txt ..\test_results\%folderName%\%%i\ +xcopy dumpfile.xml ..\test_results\%folderName%\%%i\ +xcopy mytrace.etl ..\test_results\%folderName%\%%i\ +xcopy *.man ..\test_results\%folderName%\%%i\ +del *.man +xcopy console_output.txt ..\test_results\%folderName%\%%i\ +del console_output.txt +) + +for /f "tokens=*" %%i in (gc_tests.txt) do ( +call start_trace +node --expose-gc ..\%testsDir%\%%i.js>console_output.txt +call stop_trace +mkdir ..\test_results\%folderName%\%%i\ +xcopy summary.txt ..\test_results\%folderName%\%%i\ +xcopy dumpfile.xml ..\test_results\%folderName%\%%i\ +xcopy mytrace.etl ..\test_results\%folderName%\%%i\ +xcopy *.man ..\test_results\%folderName%\%%i\ +del *.man +xcopy console_output.txt ..\test_results\%folderName%\%%i\ +del console_output.txt +) \ No newline at end of file diff --git a/etw_tools/execute.bat b/etw_tools/execute.bat new file mode 100644 index 0000000..3cbd420 --- /dev/null +++ b/etw_tools/execute.bat @@ -0,0 +1,6 @@ +call start_trace +node guid_from_names_test.js +rem node ..\test_etw\main_test.js +rem node ..\test_etw\fire_on_probe.js +call stop_trace +pause \ No newline at end of file diff --git a/etw_tools/execute_multitrace.bat b/etw_tools/execute_multitrace.bat new file mode 100644 index 0000000..a0502c6 --- /dev/null +++ b/etw_tools/execute_multitrace.bat @@ -0,0 +1,3 @@ +call start_multitrace +node win_tests/disambiguation_fire.js +call stop_multitrace \ No newline at end of file diff --git a/etw_tools/execute_tests.bat b/etw_tools/execute_tests.bat new file mode 100644 index 0000000..c23364d --- /dev/null +++ b/etw_tools/execute_tests.bat @@ -0,0 +1,10 @@ +set folderName=%time:~0,2%_%time:~3,2%__%date:~4,2%_%date:~7,2%_%date:~10,4% +set testsDir=tests + +call auto_testing.bat>output.txt + +xcopy output.txt ..\test_results\%folderName%\ +del output.txt +del mytrace.etl +del summary.txt +del dumpfile.xml \ No newline at end of file diff --git a/etw_tools/gc_tests.txt b/etw_tools/gc_tests.txt new file mode 100644 index 0000000..a2ec624 --- /dev/null +++ b/etw_tools/gc_tests.txt @@ -0,0 +1,4 @@ +gc +gc_fire +gc2 +gc3 \ No newline at end of file diff --git a/etw_tools/start_multitrace.bat b/etw_tools/start_multitrace.bat new file mode 100644 index 0000000..0dfa580 --- /dev/null +++ b/etw_tools/start_multitrace.bat @@ -0,0 +1,5 @@ +del "mytrace.etl" +logman start mytrace1 -p {5A391F32-A079-42E1-97B1-8A45295AA1FD} -o mytrace1.etl -ets +logman start mytrace2 -p {6A391F32-A079-42E1-97B1-8A45295AA1FD} -o mytrace1.etl -ets +logman start mytrace3 -p {7A391F32-A079-42E1-97B1-8A45295AA1FD} -o mytrace3.etl -ets +logman start mytrace4 -p {8A391F32-A079-42E1-97B1-8A45295AA1FD} -o mytrace4.etl -ets \ No newline at end of file diff --git a/etw_tools/start_trace.bat b/etw_tools/start_trace.bat new file mode 100644 index 0000000..7bb963f --- /dev/null +++ b/etw_tools/start_trace.bat @@ -0,0 +1,2 @@ +del "mytrace.etl" +logman start mytrace -p {5A391F32-A079-42E1-97B1-8A45295AA1FD} -o mytrace.etl -ets \ No newline at end of file diff --git a/etw_tools/stop_multitrace.bat b/etw_tools/stop_multitrace.bat new file mode 100644 index 0000000..8057e6a --- /dev/null +++ b/etw_tools/stop_multitrace.bat @@ -0,0 +1,18 @@ +logman stop mytrace1 -ets +logman stop mytrace2 -ets +logman stop mytrace3 -ets +logman stop mytrace4 -ets + +del "multi_summary1.txt" +del "multi_dump1.xml" +del "multi_summary2.txt" +del "multi_dump2.xml" +del "multi_summary3.txt" +del "multi_dump3.xml" +del "multi_summary4.txt" +del "multi_dump4.xml" + +tracerpt mytrace1.etl -o multi_dump1.xml -summary multi_summary1.txt +tracerpt mytrace1.etl -o multi_dump2.xml -summary multi_summary2.txt +tracerpt mytrace3.etl -o multi_dump3.xml -summary multi_summary3.txt +tracerpt mytrace4.etl -o multi_dump4.xml -summary multi_summary4.txt \ No newline at end of file diff --git a/etw_tools/stop_trace.bat b/etw_tools/stop_trace.bat new file mode 100644 index 0000000..59abc24 --- /dev/null +++ b/etw_tools/stop_trace.bat @@ -0,0 +1,4 @@ +logman stop mytrace -ets +del "summary.txt" +del "dumpfile.xml" +tracerpt mytrace.etl \ No newline at end of file diff --git a/etw_tools/tests.txt b/etw_tools/tests.txt new file mode 100644 index 0000000..e9b654c --- /dev/null +++ b/etw_tools/tests.txt @@ -0,0 +1,17 @@ +32probe_fire +32probe-char_fire +added-again_fire +add-probes_fire +basic_fire +cleaning-after-fire +disabledagain_fire +enabledagain_fire +enabled-disabled_fire +fewer-args_fire +fewer-args-json_fire +firing-without-enabling +json-args_fire +more-args_fire +multiple-json-args_fire +removed_fire +types_testing \ No newline at end of file diff --git a/package.json b/package.json index fc865e3..8ec6904 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,19 @@ { "name" : "dtrace-provider", "version" : "0.2.8", - "description" : "Native DTrace providers for node.js applications", - "keywords" : ["dtrace"], + "description" : "Native trace providers for node.js applications", + "keywords" : ["ETW", "dtrace", "probe"], "homepage" : "https://github.com/chrisa/node-dtrace-provider#readme", "author" : { "name" : "Chris Andrews", "email": "chris@nodnol.org" }, + "contributors" : [ { + "name" : "Alexey Ignashin", + "email": "alexey.ignashin@akvelon.com" }, { + "name" : "Henry Rawas", + "email": "henryr@schakra.com" } + ], "repository" : { "type" : "git", "url" : "http://github.com/chrisa/node-dtrace-provider.git" @@ -19,4 +25,4 @@ "tap": ">=0.2.0" }, "main" : "./dtrace-provider.js" -} +} \ No newline at end of file diff --git a/dtrace_argument.cc b/src/dtrace/dtrace_argument.cc similarity index 100% rename from dtrace_argument.cc rename to src/dtrace/dtrace_argument.cc diff --git a/dtrace_probe.cc b/src/dtrace/dtrace_probe.cc similarity index 100% rename from dtrace_probe.cc rename to src/dtrace/dtrace_probe.cc diff --git a/src/dtrace/dtrace_provider.cc b/src/dtrace/dtrace_provider.cc new file mode 100644 index 0000000..0362ded --- /dev/null +++ b/src/dtrace/dtrace_provider.cc @@ -0,0 +1,336 @@ +#include "../shared_defs.h" +#include "dtrace_provider.h" +#include + +#include +#include + +#define NAMES_FROM_GUID_FUNCTION_NAME "namesFromGuid" + +namespace node { + + using namespace v8; + + DTraceProvider::DTraceProvider() : ObjectWrap() { + provider = NULL; + } + + DTraceProvider::~DTraceProvider() { + usdt_provider_disable(provider); + usdt_provider_free(provider); + } + + bool DTraceProvider::GenerateNamesFromGuid(std::string guid, std::pair& result) { + //Remove the group separators. + while(true) { + std::string::iterator position = std::find(guid.begin(), guid.end(), '-'); + if(position == guid.end()) { + break; + } + guid.erase(position); + } + + //The fixed GUID string is expected to contain exactly 32 characters. + if(guid.length() != 32) { + return false; + } + + //Prepare the names. + result = std::pair("provider_", "module_"); + + //Concat the prepared names and the GUID: the first half of the GUID goes for the provider name, the second half goes for the module name. + result.first += guid.substr(0, 16); + result.second += guid.substr(16, 16); + + return true; + } + + Persistent DTraceProvider::constructor_template; + + void DTraceProvider::Initialize(Handle target) { + HandleScope scope; + + Local t = FunctionTemplate::New(DTraceProvider::New); + constructor_template = Persistent::New(t); + constructor_template->InstanceTemplate()->SetInternalFieldCount(1); + constructor_template->SetClassName(String::NewSymbol(PROVIDER_CREATOR_FUNCTION_NAME)); + + NODE_SET_PROTOTYPE_METHOD(constructor_template, ADD_PROBE_FUNCTION_NAME, DTraceProvider::AddProbe); + NODE_SET_PROTOTYPE_METHOD(constructor_template, REMOVE_PROBE_FUNCTION_NAME, DTraceProvider::RemoveProbe); + NODE_SET_PROTOTYPE_METHOD(constructor_template, ENABLE_FUNCTION_NAME, DTraceProvider::Enable); + NODE_SET_PROTOTYPE_METHOD(constructor_template, DISABLE_FUNCTION_NAME, DTraceProvider::Disable); + NODE_SET_PROTOTYPE_METHOD(constructor_template, FIRE_FUNCTION_NAME, DTraceProvider::Fire); + + target->Set(String::NewSymbol(PROVIDER_CREATOR_FUNCTION_NAME), constructor_template->GetFunction()); + target->Set(String::NewSymbol(NAMES_FROM_GUID_FUNCTION_NAME), FunctionTemplate::New(NamesFromGuid)->GetFunction()); + + DTraceProbe::Initialize(target); + } + + Handle DTraceProvider::NamesFromGuid(const v8::Arguments& args) { + HandleScope scope; + if(args.Length() != 1 || !args[0]->IsString()) { + return ThrowException(Exception::Error(String::New( + "Expected only one string argument"))); + } + + String::AsciiValue guid(args[0]->ToString()); + std::string guid_name(*guid); + + std::pair result; + if(!GenerateNamesFromGuid(guid_name, result)) { + return ThrowException(Exception::Error(String::New( + "Incorrect GUID format given!"))); + } + + Handle result_array = Array::New(2); + result_array->Set(0, String::New(result.first.c_str())); + result_array->Set(1, String::New(result.second.c_str())); + + return scope.Close(result_array); + } + + Handle DTraceProvider::New(const Arguments& args) { + HandleScope scope; + DTraceProvider *p = new DTraceProvider(); + char module[128]; + + bool has_object = false; + + p->Wrap(args.This()); + + if (args.Length() < 1) { + return ThrowException(Exception::Error(String::New( + "Must give provider name or object with the guid, provider_name and module_name properties as argument"))); + } + + if(!args[0]->IsString()) { + has_object = true; + } + + std::string provider_name; + std::string module_name; + + //There may be 3 different cases here. + + //CASE 1: We have not been given an object with properties, handle the string names as before. + if(!has_object) { + String::AsciiValue name(args[0]->ToString()); + + if (args.Length() == 2) { + if (!args[1]->IsString()) { + return ThrowException(Exception::Error(String::New( + "Must give module name as argument"))); + } + + String::AsciiValue mod(args[1]->ToString()); + (void) snprintf(module, sizeof (module), "%s", *mod); + } else if (args.Length() == 1) { + // If no module name is provided, develop a synthetic module name based + // on our address + (void) snprintf(module, sizeof (module), "mod-%p", p); + } else { + return ThrowException(Exception::Error(String::New( + "Expected only provider name and module as arguments"))); + } + + provider_name = *name; + module_name = module; + } else { //Otherwise, extract data from the object. + Local guid_property_key = String::New(PROPERTY_GUID); + Local provider_name_property_key = String::New(PROPERTY_PROVIDER_NAME); + Local module_name_property_key = String::New(PROPERTY_MODULE_NAME); + Handle property_value; + + Local argument = args[0]->ToObject(); + + //CASE 2: We have been given an object with the names and maybe a GUID. Just extract the names and ignore the GUID in this case. + if(argument->Has(provider_name_property_key)) { + if(!argument->Get(provider_name_property_key)->IsString()) { + return ThrowException(Exception::Error(String::New( + "Property value must be a string"))); + } else { + //Extract the provider name. + property_value = Handle::Cast(argument->Get(provider_name_property_key)); + String::AsciiValue provider(property_value); + provider_name = *provider; + + //Check is the user passed the optional module name. + if(argument->Has(module_name_property_key)) { + if(!argument->Get(module_name_property_key)->IsString()) { + return ThrowException(Exception::Error(String::New( + "Property value must be a string"))); + } else { + //Extract the module name string. + property_value = Handle::Cast(argument->Get(module_name_property_key)); + String::AsciiValue module(property_value); + module_name = *module; + } + } + } + } else { //CASE 3: We have been given an object, but it doesn't contain the properties for the names. + //Check if we have the GUID first. + if(argument->Has(guid_property_key)) { + //If the property is not a string, throw an exception. + if(!argument->Get(guid_property_key)->IsString()) { + return ThrowException(Exception::Error(String::New( + "Property value must be a string"))); + } else { + //Extract the string. + property_value = Handle::Cast(argument->Get(guid_property_key)); + String::AsciiValue string_guid(property_value); + std::string guid(*string_guid); + std::pair result; + + if(!GenerateNamesFromGuid(guid, result)) { + return ThrowException(Exception::Error(String::New( + "Incorrect GUID format given!"))); + } + + provider_name = result.first; + module_name = result.second; + } + } + } + } + + if ((p->provider = usdt_create_provider(provider_name.c_str(), module_name.c_str())) == NULL) { + return ThrowException(Exception::Error(String::New( + "usdt_create_provider failed"))); + } + + return args.This(); + } + + Handle DTraceProvider::AddProbe(const Arguments& args) { + HandleScope scope; + const char *types[USDT_ARG_MAX]; + + Handle obj = args.Holder(); + DTraceProvider *provider = ObjectWrap::Unwrap(obj); + + // create a DTraceProbe object + Handle klass = DTraceProbe::constructor_template->GetFunction(); + Handle pd = Local::New(klass->NewInstance()); + + // store in provider object + DTraceProbe *probe = ObjectWrap::Unwrap(pd->ToObject()); + obj->Set(args[0]->ToString(), pd); + + int index_offset = 1; + + //If we have received a descriptor array for ETW - ignore it and adjust the index. + if(args.Length() >= 2 && args[1]->IsArray()) { + index_offset = 2; + } + + // add probe to provider + for (int i = 0; i < USDT_ARG_MAX; i++) { + if (i < args.Length() - index_offset) { + String::AsciiValue type(args[i + index_offset]->ToString()); + + if (strncmp("json", *type, 4) == 0) + probe->arguments[i] = new DTraceJsonArgument(); + else if (strncmp("char *", *type, 6) == 0 || + strncmp("wchar_t *", *type, 9) == 0) + probe->arguments[i] = new DTraceStringArgument(); + else if (strncmp("int", *type, 3) == 0 || + strncmp("int8", *type, 4) == 0 || + strncmp("int16", *type, 5) == 0 || + strncmp("int32", *type, 5) == 0 || + strncmp("int64", *type, 5) == 0 || + strncmp("uint", *type, 4) == 0 || + strncmp("uint8", *type, 5) == 0 || + strncmp("uint16", *type, 6) == 0 || + strncmp("uint32", *type, 6) == 0 || + strncmp("uint64", *type, 6) == 0) + probe->arguments[i] = new DTraceIntegerArgument(); + else + probe->arguments[i] = new DTraceStringArgument(); + + types[i] = strdup(probe->arguments[i]->Type()); + probe->argc++; + } + } + + String::AsciiValue name(args[0]->ToString()); + probe->probedef = usdt_create_probe(*name, *name, probe->argc, types); + usdt_provider_add_probe(provider->provider, probe->probedef); + + for (size_t i = 0; i < probe->argc; i++) { + free((char *)types[i]); + } + + return pd; + } + + Handle DTraceProvider::RemoveProbe(const Arguments& args) { + HandleScope scope; + + Handle provider_obj = args.Holder(); + DTraceProvider *provider = ObjectWrap::Unwrap(provider_obj); + + Handle probe_obj = Local::Cast(args[0]); + DTraceProbe *probe = ObjectWrap::Unwrap(probe_obj); + + Handle name = String::New(probe->probedef->name); + provider_obj->Delete(name); + + if (usdt_provider_remove_probe(provider->provider, probe->probedef) != 0) + return ThrowException(Exception::Error(String::New(usdt_errstr(provider->provider)))); + + return True(); + } + + Handle DTraceProvider::Enable(const Arguments& args) { + HandleScope scope; + DTraceProvider *provider = ObjectWrap::Unwrap(args.Holder()); + + if (usdt_provider_enable(provider->provider) != 0) + return ThrowException(Exception::Error(String::New(usdt_errstr(provider->provider)))); + + return Undefined(); + } + + Handle DTraceProvider::Disable(const Arguments& args) { + HandleScope scope; + DTraceProvider *provider = ObjectWrap::Unwrap(args.Holder()); + + if (usdt_provider_disable(provider->provider) != 0) + return ThrowException(Exception::Error(String::New(usdt_errstr(provider->provider)))); + + return Undefined(); + } + + Handle DTraceProvider::Fire(const Arguments& args) { + HandleScope scope; + + if (!args[0]->IsString()) { + return ThrowException(Exception::Error(String::New( + "Must give probe name as first argument"))); + } + + if (!args[1]->IsFunction()) { + return ThrowException(Exception::Error(String::New( + "Must give probe value callback as second argument"))); + } + + Handle provider = args.Holder(); + Handle probe = Local::Cast(provider->Get(args[0])); + + DTraceProbe *p = ObjectWrap::Unwrap(probe); + if (p == NULL) + return Undefined(); + + p->_fire(args[1]); + + return True(); + } + + extern "C" void + init(Handle target) { + DTraceProvider::Initialize(target); + } + + NODE_MODULE(TraceProviderBindings, init) +} // namespace node diff --git a/dtrace_provider.h b/src/dtrace/dtrace_provider.h similarity index 91% rename from dtrace_provider.h rename to src/dtrace/dtrace_provider.h index 99dbf39..319767a 100644 --- a/dtrace_provider.h +++ b/src/dtrace/dtrace_provider.h @@ -15,6 +15,9 @@ extern "C" { #include #include +#include +#include + #ifndef __APPLE__ #include #include @@ -90,10 +93,12 @@ namespace node { static v8::Handle Enable(const v8::Arguments& args); static v8::Handle Disable(const v8::Arguments& args); static v8::Handle Fire(const v8::Arguments& args); - + static v8::Handle NamesFromGuid(const v8::Arguments& args); + DTraceProvider(); ~DTraceProvider(); private: + static bool GenerateNamesFromGuid(std::string guid, std::pair& result); static Persistent constructor_template; }; diff --git a/src/etw_win/etw_dll_manager.h b/src/etw_win/etw_dll_manager.h new file mode 100644 index 0000000..10bcf37 --- /dev/null +++ b/src/etw_win/etw_dll_manager.h @@ -0,0 +1,156 @@ +/* +Copyright (c) Microsoft Open Technologies, Inc. +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#pragma once +#include +#define MAKE_ERROR_MESSAGE(error_msg) "Failed to write ETW event: "##error_msg +/* + * Handles all interactions with WinAPI. + */ +class EtwDllManager { + typedef ULONG (NTAPI *EventRegisterFunc)(LPCGUID ProviderId, PENABLECALLBACK EnableCallback, PVOID CallbackContext, PREGHANDLE RegHandle); + typedef ULONG (NTAPI *EventUnregisterFunc)(REGHANDLE RegHandle); + typedef ULONG (NTAPI *EventWriteFunc)(REGHANDLE RegHandle, PCEVENT_DESCRIPTOR EventDescriptor, ULONG UserDataCount,PEVENT_DATA_DESCRIPTOR UserData); + + std::unordered_map m_provider_storage; + + HMODULE m_dll_handle; + + EventRegisterFunc m_event_register_ptr; + EventUnregisterFunc m_event_unregister_ptr; + EventWriteFunc m_event_write_ptr; + + // This callback is called by ETW when the consumers of our provider are enabled or disabled. + // The callback is dispatched on the ETW thread. + static void NTAPI EtwEnableCallback( + LPCGUID SourceId, + ULONG IsEnabled, + UCHAR Level, + ULONGLONG MatchAnyKeyword, + ULONGLONG MatchAllKeywords, + PEVENT_FILTER_DESCRIPTOR FilterData, + PVOID CallbackContext) { + if (CallbackContext) { + RealProvider* provider = (RealProvider*)CallbackContext; + provider->OnEtwStatusChanged(IsEnabled); + } + } + + bool InitLibrary() { + m_dll_handle = LoadLibrary("advapi32.dll"); + if (m_dll_handle) { + m_event_register_ptr = (EventRegisterFunc) GetProcAddress(m_dll_handle, "EventRegister"); + m_event_unregister_ptr = (EventUnregisterFunc) GetProcAddress(m_dll_handle, "EventUnregister"); + m_event_write_ptr = (EventWriteFunc) GetProcAddress(m_dll_handle, "EventWrite"); + } + + if (!m_dll_handle || !m_event_register_ptr || !m_event_unregister_ptr || !m_event_write_ptr) { + throw eError("Failed trying to load Windows ETW API"); + } + return true; + } + +public: + EtwDllManager(): m_dll_handle(nullptr), m_event_register_ptr(nullptr), m_event_unregister_ptr(nullptr), m_event_write_ptr(nullptr) {} + + bool IsRegistered(RealProvider* provider) { + auto iter = m_provider_storage.find(provider); + if(iter == m_provider_storage.end()) { + return false; //Most likely, the provider has been disabled. + } else { + return true; + } + } + + void Release() { + if (m_dll_handle) { + FreeLibrary(m_dll_handle); + m_dll_handle = 0; + m_event_register_ptr = nullptr; + m_event_unregister_ptr = nullptr; + m_event_write_ptr = nullptr; + } + } + + void Enable(UUID* guid, RealProvider* provider) { + //Already registered. + if (!m_dll_handle) { + if(!InitLibrary()) { + throw eError("Failed to init the library"); + } + } + + auto iter = m_provider_storage.find(provider); + if(iter != m_provider_storage.end()) { + return; + } + + REGHANDLE handle; + DWORD status = m_event_register_ptr(guid, EtwEnableCallback, provider, &handle); + if (status != ERROR_SUCCESS) { + throw eError("Failed trying to register ETW event provider"); + } + + m_provider_storage.insert(std::make_pair(provider, handle)); + return; + } + + void Disable(RealProvider* provider) { + auto iter = m_provider_storage.find(provider); + if(iter == m_provider_storage.end()) { + return; //If the specified provider is not in the storage, it has already been disabled, do nothing. + } + + if (m_dll_handle && m_event_unregister_ptr) { + m_event_unregister_ptr(iter->second); + iter->second = 0; + } + + m_provider_storage.erase(provider); //Just remove the pointer and let the V8 garbage collector deal with the object. + } + + void Write(RealProvider* provider, PCEVENT_DESCRIPTOR descriptor, ULONG user_data_count, PEVENT_DATA_DESCRIPTOR user_data) { + auto iter = m_provider_storage.find(provider); + if(iter == m_provider_storage.end()) { + throw eError("provider is not found"); + } + + ULONG result = m_event_write_ptr(iter->second, descriptor, user_data_count, user_data); + const char* error; + switch(result) { + case ERROR_SUCCESS: + return; + case ERROR_INVALID_PARAMETER: + error = MAKE_ERROR_MESSAGE("one or more of the parameters is not valid");; + break; + case ERROR_INVALID_HANDLE: + error = MAKE_ERROR_MESSAGE("the registration handle of the provider is not valid"); + break; + case ERROR_ARITHMETIC_OVERFLOW: + error = MAKE_ERROR_MESSAGE("the event size is larger than the allowed maximum (64k - header)"); + break; + case ERROR_MORE_DATA: + error = MAKE_ERROR_MESSAGE("the session buffer size is too small for the event"); + break; + case ERROR_NOT_ENOUGH_MEMORY: + error = MAKE_ERROR_MESSAGE("Occurs when filled buffers are trying to flush to disk, but disk IOs are not happening fast enough. This happens when the disk is slow and event traffic is heavy. Eventually, there are no more free (empty) buffers and the event is dropped."); + break; + case ERROR_LOG_FILE_FULL: + error = MAKE_ERROR_MESSAGE("The real-time playback file is full. Events are not logged to the session until a real-time consumer consumes the events from the playback file."); + break; + } + + throw eError(error); + } +}; \ No newline at end of file diff --git a/src/etw_win/etw_probe.h b/src/etw_win/etw_probe.h new file mode 100644 index 0000000..e0f48cc --- /dev/null +++ b/src/etw_win/etw_probe.h @@ -0,0 +1,174 @@ +/* +Copyright (c) Microsoft Open Technologies, Inc. +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#pragma once +#include +#include +#include "provider_types.h" +#include "v8_compatibility.h" +#include +#include + +using namespace node; +using namespace v8; + +#define PROBE_CLASS_NAME "Probe" + +/* + * Exposable implementation stores a pointer to the actual implementation for the V8 object. + * Aggregation ensures that the pointer to this implementation will always be valid + * even if V8 GC moves around the containing object in memory. + * Wrapping the implementation in shared_ptr ensures that when the containing object (V8Provider) for the provider is deleted by the GC + * its implementation will live for as long as there are probes bound to this implementation. + * When these probes are deleted, the provider implementation will be deleted as well. + */ +template +class ExposableImplementation: protected ObjectWrap { + std::shared_ptr m_implementation; +public: + ExposableImplementation(T* implementation): m_implementation(implementation) {} + T* GetImplementation() { + return m_implementation.get(); + } + std::shared_ptr GetSharedImplementation() { + return m_implementation; + } +}; + +class RealProbe; + +class V8Probe: public ExposableImplementation { +public: + V8Probe(RealProbe* implementation, ProbeArgumentsTypeInfo& types): ExposableImplementation(implementation) { + m_type_info = std::move(types); + + m_type_info.DoForEach([&](EventPayloadType type) { + IArgumentValue* argument_value = nullptr; + switch (type) { + case EDT_JSON: + case EDT_STRING: argument_value = new ArgumentValue(); break; + case EDT_WSTRING: argument_value = new ArgumentValue(); break; + case EDT_INT32: argument_value = new ArgumentValue(); break; + case EDT_INT8: argument_value = new ArgumentValue(); break; + case EDT_INT16: argument_value = new ArgumentValue(); break; + case EDT_INT64: argument_value = new ArgumentValue(); break; + case EDT_UINT32: argument_value = new ArgumentValue(); break; + case EDT_UINT8: argument_value = new ArgumentValue(); break; + case EDT_UINT16: argument_value = new ArgumentValue(); break; + case EDT_UINT64: argument_value = new ArgumentValue(); break; + default: + break; + } + m_argument_values.push_back(std::unique_ptr(argument_value)); + }); + } + + static Handle New(V8Probe* probe); + + DEFINE_V8_CALLBACK(Fire) + /* + * This function extracts the data fired from JS and converts it into + * the representation WinAPI expects. + */ + void FillArguments(const Local& input); + + class eEmptyArrayPassed {}; + class eArrayTooLarge {}; + class eArgumentTypesMismatch { + public: + eArgumentTypesMismatch(int argument_index): m_failed_argument_number(argument_index) {} + int m_failed_argument_number; + }; + +private: + template + inline void SetValueHelper(IArgumentValue* argument, const S& value) { + T val = (T) value; + argument->SetValue(&value); + } + + ProbeArgumentsTypeInfo m_type_info; + std::vector> m_argument_values; +}; + +class RealProvider; + +/* +RealProbe class represents a single event/probe that was added. +It knows about the arguments the probe has and contains a storage for their values. +The storage is allocated only once and remains in this state until the probe is destroyed. +*/ +class RealProbe { +public: + RealProbe(const char* event_name, EVENT_DESCRIPTOR* descriptor, int arguments_number); + RealProbe(const char* event_name, int event_id, int arguments_number); + ~RealProbe(); + + void Fire(); + bool IsBound() const; + void Bind(const std::shared_ptr& provider); + void Unbind(); + + /* + * Stores the data so it could be used when writing events. + */ + void SetArgumentValue(IArgumentValue* argument, unsigned int index) { + EVENT_DATA_DESCRIPTOR* descriptor = m_payload_descriptors.get() + index; + EventDataDescCreate(descriptor, argument->GetValue(), argument->GetSize()); + } + +private: + int m_arguments_number; + EVENT_DESCRIPTOR m_event_descriptor; + std::unique_ptr m_payload_descriptors; + std::string m_event_name; + std::shared_ptr m_owner; +}; + +/* + * Handles JSON serialization. + */ +class JSONHandler { + Persistent m_json_holder; + Persistent m_stringify_holder; + +public: + JSONHandler() { + HandleScope scope; + Handle context = Context::GetCurrent(); + Handle global = context->Global(); + Handle l_JSON = global->Get(String::New("JSON"))->ToObject(); + Handle l_JSON_stringify = Handle::Cast(l_JSON->Get(String::New("stringify"))); + + WrapInPersistent(m_json_holder, l_JSON); + WrapInPersistent(m_stringify_holder, l_JSON_stringify); + } + + std::string Stringify(Handle value) { + HandleScope scope; + + Handle args[1] = {value}; + +#ifndef BUILD_PRE011_COMPATIBILITY + Local js_stringify = Local::New(Isolate::GetCurrent(), m_stringify_holder); + Local js_object = Local::New(Isolate::GetCurrent(), m_json_holder); + Local j = js_stringify->Call(js_object, 1, args); +#else + Local j = m_stringify_holder->Call(m_json_holder, 1, args); +#endif + + String::AsciiValue str(j->ToString()); + return std::string(*str); + } +}; \ No newline at end of file diff --git a/src/etw_win/etw_provider.cc b/src/etw_win/etw_provider.cc new file mode 100644 index 0000000..32dd8d4 --- /dev/null +++ b/src/etw_win/etw_provider.cc @@ -0,0 +1,89 @@ +/* +Copyright (c) Microsoft Open Technologies, Inc. +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include "etw_provider.h" +#include "etw_dll_manager.h" +#include "manifest_builder.h" +#include +#include + +EtwDllManager g_dll_manager; + +RealProvider::RealProvider(const GUID& guid): m_enabled_status(false), m_max_event_id(1) { + memcpy(&m_guid, &guid, sizeof(GUID)); +} + +RealProvider::~RealProvider() { + m_enabled_status = false; + g_dll_manager.Disable(this); +} + +RealProbe* RealProvider::AddProbe(const char* event_name, EVENT_DESCRIPTOR* descriptor, ProbeArgumentsTypeInfo& datatypes) { + int result_id; + + if(descriptor) { + if (descriptor->Id >= m_max_event_id) { + m_max_event_id = descriptor->Id + 1; + } + result_id = descriptor->Id; + } else { + result_id = m_max_event_id; + } + + try { + g_manifest_builder.MakeProbeRecord(m_guid, event_name, result_id, datatypes); + } catch(const ManifestBuilder::eRecordExists&) { + throw eError("A probe with this name already exists"); + } + + std::unique_ptr probe; + + if(descriptor) { + probe.reset(new RealProbe(event_name, descriptor, datatypes.GetArgsNumber())); + } else { + probe.reset(new RealProbe(event_name, m_max_event_id, datatypes.GetArgsNumber())); + m_max_event_id++; + } + + return probe.release(); +} + +bool RealProvider::RemoveProbe(RealProbe* probe) { + probe->Unbind(); + return true; +} + +void RealProvider::Enable(void) { + try { + g_dll_manager.Enable(&m_guid, this); + } catch (const eError& ex) { + throw(ex); + } +} + +void RealProvider::Disable() { + g_dll_manager.Disable(this); +} + +void RealProvider::Fire(RealProbe* probe) { + if (!m_enabled_status) { + return; + } + + try { + probe->Fire(); + } catch(const eError& ex) { + throw(ex); + } +} \ No newline at end of file diff --git a/src/etw_win/etw_provider.h b/src/etw_win/etw_provider.h new file mode 100644 index 0000000..f0a2c88 --- /dev/null +++ b/src/etw_win/etw_provider.h @@ -0,0 +1,146 @@ +/* +Copyright (c) Microsoft Open Technologies, Inc. +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#pragma once +#define BUILDING_NODE_EXTENSION +#include +#include +#include +#include +#include "../shared_defs.h" +#include "provider_types.h" +#include "etw_probe.h" +#include "v8_compatibility.h" + +using namespace node; +using namespace v8; + +#define GUID_FROM_NAMES_FUNCTION_NAME "guidFromNames" + +class RealProvider; +/* +An intermediary class used to abstract from V8 API differences. +It handles all V8-specific actions and calls the appropriate methods +of the implementation to handle platform-specific actions. +*/ +class V8Provider: public ExposableImplementation { +private: + /* + * PayloadMap converts string literals received as the first argument from the addProbe JS function to named constants. + * This class is also needed to screen off all invalid (unsupported) argument types received from the addProbe function. + */ + class PayloadMap { + std::unordered_map m_data_map; + public: + PayloadMap() { + m_data_map["char *"] = EDT_STRING; + m_data_map["wchar_t *"] = EDT_WSTRING; + m_data_map["json"] = EDT_JSON; + m_data_map["int"] = EDT_INT32; + m_data_map["int32"] = EDT_INT32; + m_data_map["int8"] = EDT_INT8; + m_data_map["int16"] = EDT_INT16; + m_data_map["int64"] = EDT_INT64; + m_data_map["uint"] = EDT_UINT32; + m_data_map["uint32"] = EDT_UINT32; + m_data_map["uint8"] = EDT_UINT8; + m_data_map["uint16"] = EDT_UINT16; + m_data_map["uint64"] = EDT_UINT64; + } + + EventPayloadType ExtractPayloadType(const char* string_type) const { + auto iter = m_data_map.find(string_type); + if(iter == m_data_map.end()) + return EDT_UNKNOWN; + else + return (*iter).second; + } + }; + + static PayloadMap m_payload_map; + enum { + DESCRIPTOR_ARRAY_SIZE = 7 + }; + +public: + DEFINE_V8_CALLBACK(New) + DEFINE_V8_CALLBACK(AddProbe) + DEFINE_V8_CALLBACK(RemoveProbe) + DEFINE_V8_CALLBACK(Enable) + DEFINE_V8_CALLBACK(Disable) + DEFINE_V8_CALLBACK(Fire) + + V8Provider(RealProvider* implementation): ExposableImplementation(implementation) {} + ~V8Provider() {} +}; + +/* +Represents one ETW provider. +The class handles all platform-specific actions +and is completely V8-agnostic. +*/ +class RealProvider { +public: + RealProvider(const GUID& guid); + ~RealProvider(); + + RealProbe* AddProbe(const char* event_name, EVENT_DESCRIPTOR* descriptor, ProbeArgumentsTypeInfo& datatypes); + bool RemoveProbe(RealProbe* probe); + void Enable(); + void Disable(); + void Fire(RealProbe* probe); + /* + * The callback is triggered by EtwDllManager whenever the state of the provider is changed. + */ + void OnEtwStatusChanged(bool is_enabled) { + m_enabled_status = is_enabled; + } + +private: + bool m_enabled_status; + int m_max_event_id; + GUID m_guid; +}; + +/* + * This class stores templates for the creator functions. + * It is also used to expose API to JS. + */ +class V8TemplateHolder { +public: + static Persistent m_provider_creator; + static Persistent m_probe_creator; + + /* + * This method is invoked each time someone tries to create a provider from JS. + * A probe is never created in this manner and there is no corresponding method for it. + */ + DEFINE_V8_CALLBACK(NewProviderInstance) + /* + * The callback for the guidFromNames function exposed to JS to generate a GUID from the names. + */ + DEFINE_V8_CALLBACK(GuidFromNames) + /* + * Prepares a template for the provider and exposes the creator function for it. + */ + static void InitProvider(Handle target); + /* + * Prepares a template for the probe. This template is used when the provider creates a probe. + */ + static void InitProbe(Handle target); + /* + * Exposes the methods the user can use from JS on the object returned by require(). + */ + static void ExposeModuleInterface(Handle target); +}; diff --git a/src/etw_win/guid_generator.h b/src/etw_win/guid_generator.h new file mode 100644 index 0000000..1329153 --- /dev/null +++ b/src/etw_win/guid_generator.h @@ -0,0 +1,138 @@ +/* +Copyright (c) Microsoft Open Technologies, Inc. +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#pragma once +#include +#include +/* + * This class helps to generate the guid from the names. + */ +class GuidGenerator { + HCRYPTPROV m_provider_handle; + HCRYPTHASH m_hash_handle; + bool m_is_context_acquired; + bool m_is_hash_created; + + void Md5Start() { + if (!CryptAcquireContext(&m_provider_handle, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + throw eError("CryptAcquireContext failed, error = " + std::to_string(GetLastError())); + } else { + m_is_context_acquired = true; + } + + if (!CryptCreateHash(m_provider_handle, CALG_MD5, 0, 0, &m_hash_handle)) { + throw eError("CryptAcquireContext failed, error = " + std::to_string(GetLastError())); + } else { + m_is_hash_created = true; + } + } + + void Md5Update(const unsigned char* data, const int size) { + if (!CryptHashData(m_hash_handle, data, size, 0)) { + throw eError("CryptHashData failed, error = " + std::to_string(GetLastError())); + } + } + + void Md5Finish(unsigned char* output) { + DWORD hash_buffer_size = 16; + if (!CryptGetHashParam(m_hash_handle, HP_HASHVAL, output, &hash_buffer_size, 0)) { + throw eError("CryptGetHashParam failed, error = " + std::to_string(GetLastError())); + } + + CryptDestroyHash(m_hash_handle); + CryptReleaseContext(m_provider_handle, 0); + } + + void GenerateMD5Hash(const unsigned char* input, int input_size, unsigned char* output) { + m_is_context_acquired = false; + m_is_hash_created = false; + try { + Md5Start(); + Md5Update(input, input_size); + Md5Finish(output); + } catch (const eError& ex) { + if(m_is_hash_created) { + CryptDestroyHash(m_hash_handle); + } + if(m_is_context_acquired) { + CryptReleaseContext(m_provider_handle, 0); + } + throw eError("Unable to generate names from GUID: " + ex.error_string); + } + } + + template T DoEndianConvert(T input) { + static_assert(std::numeric_limits::is_integer, "attempting to do endian convert on a non-integer type"); + + if(sizeof(T) == 1) + return input; + + T result; + + int8_t* result_ptr = (int8_t*) &result; + int8_t* input_ptr = (int8_t*) &input; + + int last_byte_index = sizeof(T) - 1; + for(int i = 0; i < sizeof(T); i++) { + int8_t tail_index = last_byte_index - i; + result_ptr[i] = input_ptr[tail_index]; + } + + return result; + } + +public: + GuidGenerator(): m_provider_handle(0), m_hash_handle(0), m_is_context_acquired(false), m_is_hash_created(false) {} + + std::pair GenerateGuidFromNames(const std::string& provider_name, const std::string& module_name) { + static_assert(sizeof(GUID) >= 16, "sizeof(GUID) is expected to be at least 16 bytes"); + //Concat the names of the provider and the module. + std::string input = provider_name; + + if(!module_name.empty()) { + input += module_name; + } + + unsigned char raw_hash[16]; + + std::pair result; + + //Generate a GUID. + GenerateMD5Hash((const unsigned char*)input.c_str(), input.length(), raw_hash); + memcpy(&result.first, raw_hash, sizeof(GUID)); + + //Data1 is a 4-bytes long integer, Data2 and Data3 are 2-bytes long integer, Data4 is just an array of bytes, no endian conversion is required. + result.first.Data1 = DoEndianConvert(result.first.Data1); + result.first.Data2 = DoEndianConvert(result.first.Data2); + result.first.Data3 = DoEndianConvert(result.first.Data3); + + for(int i = 0; i < sizeof(raw_hash); i++) { + //Convert each by hex value to its text upper case representation. + char text_byte[3]; + sprintf_s(text_byte, 3, "%02X", raw_hash[i]); + //... and append it to the result string. + result.second.append(text_byte, 2); + //Use '-' to separate the groups in the GUID. + switch(i + 1) { + case 4: + case 6: + case 8: + case 10: + result.second += '-'; + } + } + + return result; + } +}; \ No newline at end of file diff --git a/src/etw_win/manifest_builder.cc b/src/etw_win/manifest_builder.cc new file mode 100644 index 0000000..2d4dc27 --- /dev/null +++ b/src/etw_win/manifest_builder.cc @@ -0,0 +1,179 @@ +/* +Copyright (c) Microsoft Open Technologies, Inc. +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include "manifest_builder.h" +#include + +ManifestBuilder g_manifest_builder; +ManifestBuilder* ManifestBuilder::m_instance = nullptr; + +void ManifestBuilder::MakeProviderRecord(const GUID& guid, const std::string& name) { + //Check if this GUID has already been recorded. + auto provider = std::find_if(m_all_providers.begin(), m_all_providers.end(), [&](const ProviderInfo& info) { + if(info.m_guid == guid) { + return true; + } else { + return false; + } + }); + //If it's not been - make the record. + if(provider == m_all_providers.end()) { + m_all_providers.push_back(ProviderInfo(guid, name)); + } else { + throw eRecordExists(); //Otherwise, throw an exception: each provider is expected to have its own unique GUID. + } +} + +void ManifestBuilder::MakeProbeRecord(const GUID& owning_provider_guid, const std::string& name, int id, const ProbeArgumentsTypeInfo& arguments) { + //Using the GUID, find the provider to which this probe belongs. + auto provider = std::find_if(m_all_providers.begin(), m_all_providers.end(), [&](const ProviderInfo& info) { + if(info.m_guid == owning_provider_guid) { + return true; + } else { + return false; + } + }); + + //If the provider has not been found, return. + if(provider == m_all_providers.end()) { + return; + } + + //Check if there is a probe with this name... + auto probe = std::find_if(provider->m_associated_probes.begin(), provider->m_associated_probes.end(), [&](const ProbeInfo& info) { + if(info.m_name == name) { + return true; + } else { + return false; + } + }); + + //If there is no such a probe, make the record; throw an exception otherwise. + if(probe == provider->m_associated_probes.end()) { + provider->m_associated_probes.push_back(ProbeInfo(name, id, arguments)); + } else { + throw eRecordExists(); + } + //Now update the manifest for this provider. + UpdateManifestForProvider(*provider); +} + +void ManifestBuilder::UpdateManifestForProvider(const ProviderInfo& provider) { + GuidConverter guid_converter(provider.m_guid); + std::string guid = guid_converter.GetResult(); + + XmlDocument provider_manifest(m_xml_writer, "provider_" + guid + ".man", "instrumentationManifest"); + XmlNode* provider_node = provider_manifest.GetRootNode()->AddNewNode("instrumentation")->AddNewNode("events")->AddNewNode("provider"); + + //Add the provider attributes. + if(provider.m_name.empty()) { + provider_node->SetAttribute("name", "Node-ETWProvider"); + } else { + provider_node->SetAttribute("name", provider.m_name); + } + + provider_node->SetAttribute("guid", std::string("{" + guid + "}")); + provider_node->SetAttribute("symbol", "ProviderGuid"); + provider_node->SetAttribute("resourceFileName", ""); + provider_node->SetAttribute("messageFileName", ""); + + //Create child nodes inside + XmlNode* templates_node = provider_node->AddNewNode("templates"); + XmlNode* events_node = provider_node->AddNewNode("events"); + XmlNode* keywords_node = provider_node->AddNewNode("keywords"); + XmlNode* tasks_node = provider_node->AddNewNode("tasks"); + XmlNode* opcodes_node = provider_node->AddNewNode("opcodes"); + + //Go through each probe that belongs to this provider + for(auto probe : provider.m_associated_probes) { + //Create