]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
FS-10496: [mod_v8] Speedup JavaScript. Enabling Code Caching.
authorAndrey Volk <andywolk@gmail.com>
Wed, 12 Jul 2017 23:56:33 +0000 (02:56 +0300)
committerAndrey Volk <andywolk@gmail.com>
Tue, 5 Dec 2017 11:25:13 +0000 (14:25 +0300)
conf/curl/autoload_configs/v8.conf.xml
conf/insideout/autoload_configs/v8.conf.xml
conf/vanilla/autoload_configs/v8.conf.xml
src/mod/languages/mod_v8/conf/autoload_configs/v8.conf.xml
src/mod/languages/mod_v8/mod_v8.cpp
src/mod/languages/mod_v8/mod_v8.h
src/mod/languages/mod_v8/src/fsglobal.cpp
src/mod/languages/mod_v8/src/jsmain.cpp

index 67c1f1ca06765a1b28c177b0f122323a43ca17c1..0f57fff7419ac72d7e7e02d39d418c1830a11bd7 100644 (file)
@@ -1,5 +1,7 @@
 <configuration name="v8.conf" description="Google V8 JavaScript Plug-Ins">
   <settings>
+    <!-- <param name="script-caching" value="enabled"/> -->
+    <!-- <param name="cache-expires-sec" value="3600"/> -->
     <!-- <param name="startup-script" value="startup1.js"/> -->
     <!-- <param name="startup-script" value="startup2.js"/> -->
     <!-- <param name="xml-handler-script" value="directory.js"/> -->
index 67c1f1ca06765a1b28c177b0f122323a43ca17c1..0f57fff7419ac72d7e7e02d39d418c1830a11bd7 100644 (file)
@@ -1,5 +1,7 @@
 <configuration name="v8.conf" description="Google V8 JavaScript Plug-Ins">
   <settings>
+    <!-- <param name="script-caching" value="enabled"/> -->
+    <!-- <param name="cache-expires-sec" value="3600"/> -->
     <!-- <param name="startup-script" value="startup1.js"/> -->
     <!-- <param name="startup-script" value="startup2.js"/> -->
     <!-- <param name="xml-handler-script" value="directory.js"/> -->
index 67c1f1ca06765a1b28c177b0f122323a43ca17c1..0f57fff7419ac72d7e7e02d39d418c1830a11bd7 100644 (file)
@@ -1,5 +1,7 @@
 <configuration name="v8.conf" description="Google V8 JavaScript Plug-Ins">
   <settings>
+    <!-- <param name="script-caching" value="enabled"/> -->
+    <!-- <param name="cache-expires-sec" value="3600"/> -->
     <!-- <param name="startup-script" value="startup1.js"/> -->
     <!-- <param name="startup-script" value="startup2.js"/> -->
     <!-- <param name="xml-handler-script" value="directory.js"/> -->
index 67c1f1ca06765a1b28c177b0f122323a43ca17c1..0f57fff7419ac72d7e7e02d39d418c1830a11bd7 100644 (file)
@@ -1,5 +1,7 @@
 <configuration name="v8.conf" description="Google V8 JavaScript Plug-Ins">
   <settings>
+    <!-- <param name="script-caching" value="enabled"/> -->
+    <!-- <param name="cache-expires-sec" value="3600"/> -->
     <!-- <param name="startup-script" value="startup1.js"/> -->
     <!-- <param name="startup-script" value="startup2.js"/> -->
     <!-- <param name="xml-handler-script" value="directory.js"/> -->
index 1a73db85bf76c1a2d0e112556888418443f0bc83..2a295283dd5da0fc3a0245f28bdcac3da7a012d2 100644 (file)
@@ -109,6 +109,7 @@ SWITCH_MODULE_DEFINITION_EX(mod_v8, mod_v8_load, mod_v8_shutdown, NULL, SMODF_GL
 /* API interfaces */
 static switch_api_interface_t *jsrun_interface = NULL;
 static switch_api_interface_t *jsapi_interface = NULL;
+static switch_api_interface_t *jsmon_interface = NULL;
 
 /* Module manager for loadable modules */
 module_manager_t module_manager = { 0 };
@@ -122,11 +123,26 @@ typedef struct {
        char *xml_handler;
 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
        v8::Platform *v8platform;
+       switch_hash_t *compiled_script_hash;
+       switch_mutex_t *compiled_script_hash_mutex;
+       char *script_caching;
+       switch_time_t cache_expires_seconds;
+       bool performance_monitor;
+       switch_mutex_t *mutex;
 #endif
 } mod_v8_global_t;
 
 static mod_v8_global_t globals = { 0 };
 
+#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
+/* Struct to store cached script data */
+typedef struct {
+       std::shared_ptr<uint8_t> data;
+       int length;
+       switch_time_t compile_time;
+} v8_compiled_script_cache_t;
+#endif
+
 /* Loadable module struct, used for external extension modules */
 typedef struct {
        char *filename;
@@ -318,6 +334,14 @@ static void load_configuration(void)
                                char *var = (char *)switch_xml_attr_soft(param, "name");
                                char *val = (char *)switch_xml_attr_soft(param, "value");
 
+#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
+                               if (!strcmp(var, "script-caching")) {
+                                       globals.script_caching = switch_core_strdup(globals.pool, val);
+                               } else if (!strcmp(var, "cache-expires-sec")) {
+                                       int v = atoi(val);
+                                       globals.cache_expires_seconds = (v > 0) ? v : 0;
+                               } else 
+#endif
                                if (!strcmp(var, "xml-handler-script")) {
                                        globals.xml_handler = switch_core_strdup(globals.pool, val);
                                }
@@ -334,6 +358,12 @@ static void load_configuration(void)
                                }
                        }
 
+#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
+                       if (zstr(globals.script_caching)) {
+                               globals.script_caching = switch_core_strdup(globals.pool, "disabled");
+                       }
+#endif
+
                        for (hook = switch_xml_child(settings, "hook"); hook; hook = hook->next) {
                                char *event = (char *)switch_xml_attr_soft(hook, "event");
                                char *subclass = (char *)switch_xml_attr_soft(hook, "subclass");
@@ -462,6 +492,121 @@ static char *v8_get_script_path(const char *script_file)
        }
 }
 
+#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
+void perf_log(const char *fmt, ...)
+{
+       va_list ap;
+       va_start(ap, fmt);
+
+       switch_mutex_lock(globals.mutex);
+       if (globals.performance_monitor) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, fmt, ap);
+       }
+       switch_mutex_unlock(globals.mutex);
+
+       va_end(ap);
+}
+
+template< typename T >
+struct array_deleter
+{
+       void operator ()(T const * p)
+       {
+               delete[] p;
+       }
+};
+
+static void destructor(void *ptr)
+{
+       delete (v8_compiled_script_cache_t*)ptr;
+}
+
+void LoadScript(MaybeLocal<v8::Script> *v8_script, Isolate *isolate, const char *script_data, const char *script_file)
+{
+       switch_time_t start = switch_time_now();
+
+       ScriptCompiler::CachedData *cached_data = 0;
+       v8_compiled_script_cache_t *stored_compiled_script_cache = NULL;
+       ScriptCompiler::CompileOptions options;
+
+       /*
+               Do not cache if the caching is disabled
+               Do not cache inline scripts
+       */
+       if (!strcasecmp(globals.script_caching, "disabled") || !strcasecmp(globals.script_caching, "false") || !strcasecmp(globals.script_caching, "no") ||     !strcasecmp(script_file, "inline") || zstr(script_file)) {
+               options = ScriptCompiler::kNoCompileOptions;
+               perf_log("Javascript caching is disabled.\n", script_file);
+       } else {
+               options = ScriptCompiler::kConsumeCodeCache;
+
+               switch_mutex_lock(globals.compiled_script_hash_mutex);
+
+               void *hash_found = switch_core_hash_find(globals.compiled_script_hash, script_file);
+               if (hash_found)
+               {
+                       stored_compiled_script_cache = new v8_compiled_script_cache_t;
+                       *stored_compiled_script_cache = *((v8_compiled_script_cache_t *)hash_found);
+               }
+
+               switch_mutex_unlock(globals.compiled_script_hash_mutex);
+
+               if (stored_compiled_script_cache)
+               {
+                       switch_time_t time_left_since_compile_sec = (switch_time_now() - stored_compiled_script_cache->compile_time) / 1000000;
+                       if (time_left_since_compile_sec <= globals.cache_expires_seconds || globals.cache_expires_seconds == 0) {
+                               cached_data = new ScriptCompiler::CachedData(stored_compiled_script_cache->data.get(), stored_compiled_script_cache->length, ScriptCompiler::CachedData::BufferNotOwned);
+                       } else {
+                               perf_log("Javascript ['%s'] cache expired.\n", script_file);
+                               switch_core_hash_delete_locked(globals.compiled_script_hash, script_file, globals.compiled_script_hash_mutex);
+                       }
+
+               }
+               
+               if (!cached_data) options = ScriptCompiler::kProduceCodeCache;
+
+       }
+
+       ScriptCompiler::Source source(String::NewFromUtf8(isolate, script_data), cached_data);
+       *v8_script = ScriptCompiler::Compile(isolate->GetCurrentContext(), &source, options);   
+
+       if (!v8_script->IsEmpty()) {
+
+               if (options == ScriptCompiler::kProduceCodeCache && !source.GetCachedData()->rejected) {
+                       int length = source.GetCachedData()->length;
+                       uint8_t* raw_cached_data = new uint8_t[length];
+                       v8_compiled_script_cache_t *compiled_script_cache = new v8_compiled_script_cache_t;
+                       memcpy(raw_cached_data, source.GetCachedData()->data, static_cast<size_t>(length));
+                       compiled_script_cache->data.reset(raw_cached_data, array_deleter<uint8_t>());
+                       compiled_script_cache->length = length;
+                       compiled_script_cache->compile_time = switch_time_now();
+
+                       switch_mutex_lock(globals.compiled_script_hash_mutex);
+                       switch_core_hash_insert_destructor(globals.compiled_script_hash, script_file, compiled_script_cache, destructor);
+                       switch_mutex_unlock(globals.compiled_script_hash_mutex);
+                       
+                       perf_log("Javascript ['%s'] cache was produced.\n", script_file);
+
+               } else if (options == ScriptCompiler::kConsumeCodeCache) {
+
+                       if (source.GetCachedData()->rejected) {
+                               perf_log("Javascript ['%s'] cache was rejected.\n", script_file);
+                               switch_core_hash_delete_locked(globals.compiled_script_hash, script_file, globals.compiled_script_hash_mutex);
+                       } else {
+                               perf_log("Javascript ['%s'] execution using cache.\n", script_file);
+                       }
+
+               }
+       }
+
+       if (stored_compiled_script_cache)
+               delete stored_compiled_script_cache;
+
+       switch_time_t end = switch_time_now();
+       perf_log("Javascript ['%s'] loaded in %u microseconds.\n", script_file, (end - start));
+
+}
+#endif
+
 static int v8_parse_and_execute(switch_core_session_t *session, const char *input_code, switch_stream_handle_t *api_stream, v8_event_t *v8_event, v8_xml_handler_t* xml_handler)
 {
        string res;
@@ -653,27 +798,17 @@ static int v8_parse_and_execute(switch_core_session_t *session, const char *inpu
                                                        context->Global()->Set(String::NewFromUtf8(isolate, "scriptPath"), String::NewFromUtf8(isolate, path));
                                                        free(path);
                                                }
-                                               // Create a string containing the JavaScript source code.
-#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
-                                               ScriptCompiler::Source source(String::NewFromUtf8(isolate, script_data));
-#else
-                                               Handle<String> source = String::NewFromUtf8(isolate, script_data);
-#endif
 
                                                TryCatch try_catch;
 
                                                // Compile the source code.
 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
-                                               v8::ScriptCompiler::CompileOptions options = v8::ScriptCompiler::kNoCompileOptions;
-                                               Handle<v8::Script> v8_script;
-                                               v8::MaybeLocal<v8::Script> v8_script_check = v8::ScriptCompiler::Compile(context, &source, options);
-                                               
-                                               if (!v8_script_check.IsEmpty()) {
-                                                       v8_script = v8_script_check.ToLocalChecked();
-                                               }
-                                               //Handle<v8::Script> v8_script = v8::ScriptCompiler::Compile(context, source,/* String::NewFromUtf8(isolate, script_file),*/ v8::ScriptCompiler::kProduceCodeCache).ToLocalChecked();
-                                               //source->GetCachedData();
+                                               switch_time_t start = switch_time_now();
+                                               MaybeLocal<v8::Script> v8_script;
+                                               LoadScript(&v8_script, isolate, script_data, script_file);
 #else
+                                               // Create a string containing the JavaScript source code.
+                                               Handle<String> source = String::NewFromUtf8(isolate, script_data);
                                                Handle<Script> v8_script = Script::Compile(source, Local<Value>::New(isolate, String::NewFromUtf8(isolate, script_file)));
 #endif
 
@@ -687,7 +822,24 @@ static int v8_parse_and_execute(switch_core_session_t *session, const char *inpu
                                                                Debug::DebugBreak(isolate);
                                                        }
 #endif
+
+#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
+                                                       Handle<Value> result;
+
+                                                       if (!v8_script.IsEmpty()) {
+                                                               result = v8_script.ToLocalChecked()->Run();
+                                                       }
+
+                                                       switch_mutex_lock(globals.mutex);
+                                                       if (globals.performance_monitor) {
+                                                               switch_time_t end = switch_time_now();
+                                                               switch_time_t delay = (end - start);
+                                                               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Javascript execution time: %u microseconds\n", delay);
+                                                       }
+                                                       switch_mutex_unlock(globals.mutex);
+#else
                                                        Handle<Value> result = v8_script->Run();
+#endif
                                                        if (try_catch.HasCaught()) {
                                                                v8_error(isolate, &try_catch);
                                                        } else {
@@ -1006,6 +1158,32 @@ SWITCH_STANDARD_API(launch_async)
        return SWITCH_STATUS_SUCCESS;
 }
 
+SWITCH_STANDARD_API(jsmon_function)
+{
+       if (zstr(cmd)) {
+               stream->write_function(stream, "USAGE: %s\n", jsmon_interface->syntax);
+               return SWITCH_STATUS_SUCCESS;
+       }
+
+       if (!strcasecmp(cmd, "on")) {
+               switch_mutex_lock(globals.mutex);
+               globals.performance_monitor = true;
+               switch_mutex_unlock(globals.mutex);
+               stream->write_function(stream, "Performance monitor has been enabled.\n", jsmon_interface->syntax);
+       } else if (!strcasecmp(cmd, "off")) {
+               switch_mutex_lock(globals.mutex);
+               globals.performance_monitor = false;
+               switch_mutex_unlock(globals.mutex);
+               stream->write_function(stream, "Performance monitor has been disabled.\n", jsmon_interface->syntax);
+       } else {
+               stream->write_function(stream, "USAGE: %s\n", jsmon_interface->syntax);
+               return SWITCH_STATUS_SUCCESS;
+       }
+       
+       stream->write_function(stream, "+OK\n");
+       return SWITCH_STATUS_SUCCESS;
+}
+
 SWITCH_MODULE_LOAD_FUNCTION(mod_v8_load)
 {
        switch_application_interface_t *app_interface;
@@ -1019,6 +1197,10 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_v8_load)
 
        globals.pool = pool;
 
+#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
+       switch_mutex_init(&globals.compiled_script_hash_mutex, SWITCH_MUTEX_NESTED, globals.pool);
+       switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, globals.pool);
+#endif
        switch_mutex_init(&globals.event_mutex, SWITCH_MUTEX_NESTED, globals.pool);
        globals.event_handlers = new set<FSEventHandler *>();
 
@@ -1028,7 +1210,11 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_v8_load)
 
 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
        globals.v8platform = NULL;
+       globals.cache_expires_seconds = 0;
+       globals.performance_monitor = false;
        JSMain::Initialize(&globals.v8platform);
+
+       switch_core_hash_init(&globals.compiled_script_hash);
 #else
        JSMain::Initialize();
 #endif
@@ -1051,6 +1237,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_v8_load)
        *module_interface = switch_loadable_module_create_module_interface(pool, modname);
        SWITCH_ADD_API(jsrun_interface, "jsrun", "run a script", launch_async, "jsrun <script> [additional_vars [...]]");
        SWITCH_ADD_API(jsapi_interface, "jsapi", "execute an api call", jsapi_function, "jsapi <script> [additional_vars [...]]");
+       SWITCH_ADD_API(jsmon_interface, "jsmon", "toggle performance monitor", jsmon_function, "jsmon on|off");
        SWITCH_ADD_APP(app_interface, "javascript", "Launch JS ivr", "Run a javascript ivr on a channel", v8_dp_function, "<script> [additional_vars [...]]", SAF_SUPPORT_NOMEDIA);
        SWITCH_ADD_CHAT_APP(chat_app_interface, "javascript", "execute a js script", "execute a js script", v8_chat_function, "<script>", SCAF_NONE);
 
@@ -1071,6 +1258,10 @@ SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_v8_shutdown)
 
 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
        delete globals.v8platform;
+
+       switch_core_hash_destroy(&globals.compiled_script_hash);
+       switch_mutex_destroy(globals.compiled_script_hash_mutex);
+       switch_mutex_destroy(globals.mutex);
 #endif
 
        switch_core_hash_destroy(&module_manager.load_hash);
index 5dc344618fd08bf44e1a18b8111d512c390973b6..a333d6490dbda74c9d86eca0adf6901ea62beedb 100644 (file)
 #include "javascript.hpp"
 #include <switch.h>
 
+#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
+void LoadScript(v8::MaybeLocal<v8::Script> *v8_script, v8::Isolate *isolate, const char *script_data, const char *script_file);
+#endif
+
 SWITCH_BEGIN_EXTERN_C
 
 #define JS_BUFFER_SIZE 1024 * 32
index af841a9cc2f9ff375cb441b25f53fa0ae0510c7e..4bea1094fb2113e76d6e0dffb7331ddeba630599 100644 (file)
@@ -477,13 +477,20 @@ JS_GLOBAL_FUNCTION_IMPL_STATIC(Include)
                                string js_file = JSMain::LoadFileToString(script_name);
 
                                if (js_file.length() > 0) {
-                                       Handle<String> source = String::NewFromUtf8(info.GetIsolate(), js_file.c_str());
 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
-                                       Handle<Script> script = Script::Compile(source, info[i]->ToString());
+                                       MaybeLocal<v8::Script> script;
+                                       LoadScript(&script, info.GetIsolate(), js_file.c_str(), script_name);
+                                       
+                                       if (script.IsEmpty()) {
+                                               info.GetReturnValue().Set(false);
+                                       } else {
+                                               info.GetReturnValue().Set(script.ToLocalChecked()->Run());
+                                       }
 #else
+                                       Handle<String> source = String::NewFromUtf8(info.GetIsolate(), js_file.c_str());
                                        Handle<Script> script = Script::Compile(source, info[i]);
-#endif
                                        info.GetReturnValue().Set(script->Run());
+#endif
                                        switch_safe_free(path);
                                        return;
                                }
index 1c591627d50a7014dd79d996fe9c22b3bb9b625c..e162460df2c630db72b38d0b9e65f1dc29f2c5a2 100644 (file)
@@ -29,6 +29,9 @@
  */
 
 #include "javascript.hpp"
+#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
+#include "mod_v8.h"
+#endif
 
 #ifdef V8_ENABLE_DEBUGGING
 #include <v8-debug.h>
@@ -228,15 +231,21 @@ void JSMain::Include(const v8::FunctionCallbackInfo<Value>& args)
                string js_file = LoadFileToString(js_safe_str(*str));
 
                if (js_file.length() > 0) {
-                       Handle<String> source = String::NewFromUtf8(args.GetIsolate(), js_file.c_str());
-
 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
-                       Handle<Script> script = Script::Compile(source, args[i]->ToString());
+                       MaybeLocal<v8::Script> script;
+                       LoadScript(&script, args.GetIsolate(), js_file.c_str(), js_safe_str(*str));
+
+                       if (script.IsEmpty()) {
+                               args.GetReturnValue().Set(false);
+                       }
+                       else {
+                               args.GetReturnValue().Set(script.ToLocalChecked()->Run());
+                       }
 #else
+                       Handle<String> source = String::NewFromUtf8(args.GetIsolate(), js_file.c_str());
                        Handle<Script> script = Script::Compile(source, args[i]);
-#endif
-
                        args.GetReturnValue().Set(script->Run());
+#endif
 
                        return;
                }
@@ -314,15 +323,16 @@ const string JSMain::ExecuteString(const string& scriptData, const string& fileN
                                inst->obj->RegisterInstance(isolate, inst->name, inst->auto_destroy);
                        }
 
-                       // Create a string containing the JavaScript source code.
-                       Handle<String> source = String::NewFromUtf8(isolate, scriptData.c_str());
-
                        TryCatch try_catch;
 
                        // Compile the source code.
 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
-                       Handle<Script> script = Script::Compile(source, String::NewFromUtf8(isolate, fileName.c_str()));
+                       // Compile the source code.
+                       MaybeLocal<v8::Script> script;
+                       LoadScript(&script, isolate, scriptData.c_str(), fileName.c_str());
 #else
+                       // Create a string containing the JavaScript source code.
+                       Handle<String> source = String::NewFromUtf8(isolate, scriptData.c_str());
                        Handle<Script> script = Script::Compile(source, Local<Value>::New(isolate, String::NewFromUtf8(isolate, fileName.c_str())));
 #endif
 
@@ -330,8 +340,17 @@ const string JSMain::ExecuteString(const string& scriptData, const string& fileN
                                res = JSMain::GetExceptionInfo(isolate, &try_catch);
                                isError = true;
                        } else {
+#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
+                               // Run the script
+                               Handle<Value> result;
+
+                               if (!script.IsEmpty()) {
+                                   result = script.ToLocalChecked()->Run();
+                               }
+#else
                                // Run the script
                                Handle<Value> result = script->Run();
+#endif
                                if (try_catch.HasCaught()) {
                                        res = JSMain::GetExceptionInfo(isolate, &try_catch);
                                        isError = true;