From 3faf1985d277fc53c111464b73aac87f27158e7d Mon Sep 17 00:00:00 2001 From: Timo Paulssen Date: Wed, 28 Aug 2019 00:50:23 +0200 Subject: [PATCH] Introduce vmevent, "VM Event Subscription" Lets a ConcBlockingQueue be configured to receive a set of events from inside the VM. Currently there's only "gcevent", which gives you an object in the queue every time the GC runs. It's an int64 array containing GC sequence number, absolute start time, start time since vm started, time taken, minor or major collection, bytes promoted to the old generation, and thread id of the thread that coordinated (almost equivalent to the thread that caused GC to run in the first pace). --- lib/MAST/Ops.nqp | 8 +++---- src/6model/reprs/MVMHash.h | 4 ++-- src/core/instance.h | 16 ++++++++++++++ src/core/interp.c | 9 ++++++-- src/core/oplabels.h | 2 +- src/core/oplist | 2 +- src/core/ops.c | 6 ++--- src/core/ops.h | 2 +- src/gc/orchestrate.c | 42 ++++++++++++++++++++++++++++++++++- src/gc/roots.c | 6 +++++ src/moar.c | 45 ++++++++++++++++++++++++++++++++++++++ src/moar.h | 2 ++ src/types.h | 1 + 13 files changed, 130 insertions(+), 15 deletions(-) diff --git a/lib/MAST/Ops.nqp b/lib/MAST/Ops.nqp index 8a9c147dc4..b6a423baf1 100644 --- a/lib/MAST/Ops.nqp +++ b/lib/MAST/Ops.nqp @@ -2968,7 +2968,7 @@ BEGIN { 57, 58, 65, - 66, + 65, 65, 66, 65, @@ -4276,7 +4276,7 @@ BEGIN { 'link', 547, 'gethostname', 548, 'exreturnafterunwind', 549, - 'DEPRECATED_13', 550, + 'vmeventsubscribe', 550, 'continuationreset', 551, 'continuationcontrol', 552, 'continuationinvoke', 553, @@ -5099,7 +5099,7 @@ BEGIN { 'link', 'gethostname', 'exreturnafterunwind', - 'DEPRECATED_13', + 'vmeventsubscribe', 'continuationreset', 'continuationcontrol', 'continuationinvoke', @@ -9453,7 +9453,7 @@ BEGIN { nqp::writeuint($bytecode, $elems, 549, 5); my uint $index0 := nqp::unbox_u($op0); nqp::writeuint($bytecode, nqp::add_i($elems, 2), $index0, 5); }, - 'DEPRECATED_13', sub ($op0, $op1) { + 'vmeventsubscribe', sub ($op0, $op1) { my $bytecode := $*MAST_FRAME.bytecode; my uint $elems := nqp::elems($bytecode); nqp::writeuint($bytecode, $elems, 550, 5); diff --git a/src/6model/reprs/MVMHash.h b/src/6model/reprs/MVMHash.h index 0025801bbb..385c50c2b5 100644 --- a/src/6model/reprs/MVMHash.h +++ b/src/6model/reprs/MVMHash.h @@ -27,7 +27,7 @@ const MVMREPROps * MVMHash_initialize(MVMThreadContext *tc); HASH_ADD_KEYPTR_VM_STR(tc, hash_handle, hash, key, value); \ } \ else { \ - MVM_exception_throw_adhoc(tc, "Hash keys must be concrete strings"); \ + MVM_exception_throw_adhoc(tc, "Hash keys must be concrete strings (got %s)", MVM_6model_get_debug_name(tc, (MVMObject *)key)); \ } \ } while (0); @@ -38,7 +38,7 @@ const MVMREPROps * MVMHash_initialize(MVMThreadContext *tc); HASH_FIND_VM_STR(tc, hash_handle, hash, key, value); \ } \ else { \ - MVM_exception_throw_adhoc(tc, "Hash keys must be concrete strings"); \ + MVM_exception_throw_adhoc(tc, "Hash keys must be concrete strings (got %s)", MVM_6model_get_debug_name(tc, (MVMObject *)key)); \ } \ } while (0); diff --git a/src/core/instance.h b/src/core/instance.h index f6785c7e55..4d004ccca6 100644 --- a/src/core/instance.h +++ b/src/core/instance.h @@ -115,6 +115,16 @@ struct MVMObjectId { UT_hash_handle hash_handle; }; +struct MVMEventSubscriptions { + uv_mutex_t mutex_event_subscription; + + MVMObject *subscription_queue; + + MVMObject *GCEvent; + + MVMuint64 vm_startup_time; +}; + /* Represents a MoarVM instance. */ struct MVMInstance { /************************************************************************ @@ -530,4 +540,10 @@ struct MVMInstance { /* Hash Secrets which is used as the hash seed. This is to avoid denial of * service type attacks. */ MVMuint64 hashSecrets[2]; + + /************************************************************************ + * VM Event subscription + ************************************************************************/ + + MVMEventSubscriptions subscriptions; }; diff --git a/src/core/interp.c b/src/core/interp.c index cd523325e3..aedd917155 100644 --- a/src/core/interp.c +++ b/src/core/interp.c @@ -3934,6 +3934,13 @@ void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContex cur_op += 2; goto NEXT; } + OP(vmeventsubscribe): { + MVMObject *queue = GET_REG(cur_op, 0).o; + MVMObject *config = GET_REG(cur_op, 2).o; + MVM_vm_event_subscription_configure(tc, queue, config); + cur_op += 4; + goto NEXT; + } OP(continuationreset): { MVMRegister *res = &GET_REG(cur_op, 0); MVMObject *tag = GET_REG(cur_op, 2).o; @@ -6517,8 +6524,6 @@ void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContex OP(DEPRECATED_11): OP(DEPRECATED_12): MVM_exception_throw_adhoc(tc, "The getregref_* ops were removed in MoarVM 2017.01."); - OP(DEPRECATED_13): - MVM_exception_throw_adhoc(tc, "The continuationclone op was removed in MoarVM 2017.01."); OP(DEPRECATED_14): MVM_exception_throw_adhoc(tc, "The asyncwritestr op was removed in MoarVM 2017.05."); OP(DEPRECATED_15): diff --git a/src/core/oplabels.h b/src/core/oplabels.h index c63d80a242..0c92dd6a3e 100644 --- a/src/core/oplabels.h +++ b/src/core/oplabels.h @@ -551,7 +551,7 @@ static const void * const LABELS[] = { &&OP_link, &&OP_gethostname, &&OP_exreturnafterunwind, - &&OP_DEPRECATED_13, + &&OP_vmeventsubscribe, &&OP_continuationreset, &&OP_continuationcontrol, &&OP_continuationinvoke, diff --git a/src/core/oplist b/src/core/oplist index cf38beabe0..7320712177 100644 --- a/src/core/oplist +++ b/src/core/oplist @@ -607,7 +607,7 @@ symlink r(str) r(str) link r(str) r(str) gethostname w(str) exreturnafterunwind r(obj) -DEPRECATED_13 w(obj) r(obj) +vmeventsubscribe r(obj) r(obj) continuationreset w(obj) r(obj) r(obj) :invokish :maycausedeopt # this op isn't actually invokish, but it requires the cur_op to be set before doing its work continuationcontrol w(obj) r(int64) r(obj) r(obj) :invokish :maycausedeopt diff --git a/src/core/ops.c b/src/core/ops.c index 91e5e84efd..b84c12ba39 100644 --- a/src/core/ops.c +++ b/src/core/ops.c @@ -7698,8 +7698,8 @@ static const MVMOpInfo MVM_op_infos[] = { { MVM_operand_read_reg | MVM_operand_obj } }, { - MVM_OP_DEPRECATED_13, - "DEPRECATED_13", + MVM_OP_vmeventsubscribe, + "vmeventsubscribe", 2, 0, 0, @@ -7709,7 +7709,7 @@ static const MVMOpInfo MVM_op_infos[] = { 0, 0, 0, - { MVM_operand_write_reg | MVM_operand_obj, MVM_operand_read_reg | MVM_operand_obj } + { MVM_operand_read_reg | MVM_operand_obj, MVM_operand_read_reg | MVM_operand_obj } }, { MVM_OP_continuationreset, diff --git a/src/core/ops.h b/src/core/ops.h index 5b22670448..30ef7e8c2b 100644 --- a/src/core/ops.h +++ b/src/core/ops.h @@ -551,7 +551,7 @@ #define MVM_OP_link 547 #define MVM_OP_gethostname 548 #define MVM_OP_exreturnafterunwind 549 -#define MVM_OP_DEPRECATED_13 550 +#define MVM_OP_vmeventsubscribe 550 #define MVM_OP_continuationreset 551 #define MVM_OP_continuationcontrol 552 #define MVM_OP_continuationinvoke 553 diff --git a/src/gc/orchestrate.c b/src/gc/orchestrate.c index ca6b56d60e..8e37b02e88 100644 --- a/src/gc/orchestrate.c +++ b/src/gc/orchestrate.c @@ -392,8 +392,16 @@ static void run_gc(MVMThreadContext *tc, MVMuint8 what_to_do) { MVMuint8 gen; MVMuint32 i, n; + MVMuint8 is_coordinator; + + MVMuint64 start_time, end_time; + unsigned int interval_id; + MVMObject *subscription_queue = NULL; + + is_coordinator = what_to_do == MVMGCWhatToDo_All; + #if MVM_GC_DEBUG if (tc->in_spesh) MVM_panic(1, "Must not GC when in the specializer/JIT\n"); @@ -408,6 +416,9 @@ static void run_gc(MVMThreadContext *tc, MVMuint8 what_to_do) { interval_id = MVM_telemetry_interval_start(tc, "start minor collection"); } + if (is_coordinator) + start_time = uv_hrtime(); + /* Do GC work for ourselves and any work threads. */ for (i = 0, n = tc->gc_work_count ; i < n; i++) { MVMThreadContext *other = tc->gc_work[i].tc; @@ -419,7 +430,36 @@ static void run_gc(MVMThreadContext *tc, MVMuint8 what_to_do) { } /* Wait for everybody to agree we're done. */ - finish_gc(tc, gen, what_to_do == MVMGCWhatToDo_All); + finish_gc(tc, gen, is_coordinator); + + if (is_coordinator) + end_time = uv_hrtime(); + + /* Finally, as the very last thing ever, the coordinator pushes a bit of + * info into the subscription queue (if it is set) */ + + subscription_queue = tc->instance->subscriptions.subscription_queue; + + if (is_coordinator && subscription_queue && tc->instance->subscriptions.GCEvent) { + MVMObject *instance = MVM_repr_alloc(tc, tc->instance->subscriptions.GCEvent); + + MVMArray *arrobj = (MVMArray *)instance; + MVMuint64 *data; + + MVM_repr_pos_set_elems(tc, instance, 7); + + data = arrobj->body.slots.u64; + + data[0] = MVM_load(&tc->instance->gc_seq_number); + data[1] = start_time; + data[2] = start_time - tc->instance->subscriptions.vm_startup_time; + data[3] = end_time - start_time; + data[4] = gen == MVMGCGenerations_Both; + data[5] = tc->gc_promoted_bytes; + data[6] = tc->thread_id; + + MVM_repr_push_o(tc, tc->instance->subscriptions.subscription_queue, instance); + } MVM_telemetry_interval_stop(tc, interval_id, "finished run_gc"); } diff --git a/src/gc/roots.c b/src/gc/roots.c index c7dd95d5ef..d770b44a70 100644 --- a/src/gc/roots.c +++ b/src/gc/roots.c @@ -134,6 +134,12 @@ void MVM_gc_root_add_instance_roots_to_worklist(MVMThreadContext *tc, MVMGCWorkl if (tc->instance->confprog) MVM_confprog_mark(tc, worklist, snapshot); + add_collectable(tc, worklist, snapshot, tc->instance->subscriptions.subscription_queue, + "VM Event Subscription Queue"); + + add_collectable(tc, worklist, snapshot, tc->instance->subscriptions.GCEvent, + "VM Event GCEvent type"); + MVM_debugserver_mark_handles(tc, worklist, snapshot); } diff --git a/src/moar.c b/src/moar.c index 08d5db7aa4..a0a4ad1f74 100644 --- a/src/moar.c +++ b/src/moar.c @@ -85,6 +85,8 @@ MVMInstance * MVM_vm_create_instance(void) { /* Set up instance data structure. */ instance = MVM_calloc(1, sizeof(MVMInstance)); + instance->subscriptions.vm_startup_time = uv_hrtime(); + /* Create the main thread's ThreadContext and stash it. */ instance->main_thread = MVM_tc_create(NULL, instance); /* Get the 128-bit hashSecret */ @@ -392,6 +394,8 @@ MVMInstance * MVM_vm_create_instance(void) { /* Back to nursery allocation, now we're set up. */ MVM_gc_allocate_gen2_default_clear(instance->main_thread); + init_mutex(instance->subscriptions.mutex_event_subscription, "vm event subscription mutex"); + return instance; } @@ -661,6 +665,8 @@ void MVM_vm_destroy_instance(MVMInstance *instance) { /* Clean up fixed size allocator */ MVM_fixed_size_destroy(instance->fsa); + uv_mutex_destroy(&instance->subscriptions.mutex_event_subscription); + /* Clear up VM instance memory. */ MVM_free(instance); } @@ -678,6 +684,45 @@ void MVM_vm_set_prog_name(MVMInstance *instance, const char *prog_name) { instance->prog_name = prog_name; } +void MVM_vm_event_subscription_configure(MVMThreadContext *tc, MVMObject *queue, MVMObject *config) { + MVMString *gcevent; + + MVMROOT2(tc, queue, config, { + if (!IS_CONCRETE(config)) { + MVM_exception_throw_adhoc(tc, "vmeventsubscribe requires a concrete configuration hash (got a %s type object)", MVM_6model_get_debug_name(tc, config)); + } + + if (REPR(queue)->ID != MVM_REPR_ID_ConcBlockingQueue && !MVM_is_null(tc, queue) || !IS_CONCRETE(queue)) { + MVM_exception_throw_adhoc(tc, "vmeventsubscribe requires a concrete ConcBlockingQueue (got a %s)", MVM_6model_get_debug_name(tc, queue)); + } + + uv_mutex_lock(&tc->instance->subscriptions.mutex_event_subscription); + + if (REPR(queue)->ID == MVM_REPR_ID_ConcBlockingQueue && IS_CONCRETE(queue)) { + tc->instance->subscriptions.subscription_queue = queue; + } + + gcevent = MVM_string_utf8_decode(tc, tc->instance->VMString, "gcevent", 7); + + if (MVM_repr_exists_key(tc, config, gcevent)) { + MVMObject *value = MVM_repr_at_key_o(tc, config, gcevent); + + if (MVM_is_null(tc, value)) { + tc->instance->subscriptions.GCEvent = NULL; + } + else if (REPR(value)->ID == MVM_REPR_ID_VMArray && !IS_CONCRETE(value) && ((MVMArrayREPRData *)STABLE(value)->REPR_data)->slot_type == MVM_ARRAY_I64) { + tc->instance->subscriptions.GCEvent = value; + } + else { + uv_mutex_unlock(&tc->instance->subscriptions.mutex_event_subscription); + MVM_exception_throw_adhoc(tc, "vmeventsubscribe expects value at 'gcevent' key to be null (to unsubscribe) or a VMArray of int64 type object, got a %s%s%s (%s)", IS_CONCRETE(value) ? "concrete " : "", MVM_6model_get_debug_name(tc, value), IS_CONCRETE(value) ? "" : " type object", REPR(value)->name); + } + } + }); + + uv_mutex_unlock(&tc->instance->subscriptions.mutex_event_subscription); +} + void MVM_vm_set_lib_path(MVMInstance *instance, int count, const char **lib_path) { enum { MAX_COUNT = sizeof instance->lib_path / sizeof *instance->lib_path }; diff --git a/src/moar.h b/src/moar.h index 3434913ff1..34876215aa 100644 --- a/src/moar.h +++ b/src/moar.h @@ -231,6 +231,8 @@ MVM_PUBLIC void MVM_vm_set_exec_name(MVMInstance *instance, const char *exec_nam MVM_PUBLIC void MVM_vm_set_prog_name(MVMInstance *instance, const char *prog_name); MVM_PUBLIC void MVM_vm_set_lib_path(MVMInstance *instance, int count, const char **lib_path); +MVM_PUBLIC void MVM_vm_event_subscription_configure(MVMThreadContext *tc, MVMObject *queue, MVMObject *config); + /* Returns absolute executable path. */ MVM_PUBLIC int MVM_exepath(char* buffer, size_t* size); diff --git a/src/types.h b/src/types.h index 31d9d2b4de..2be7f774e2 100644 --- a/src/types.h +++ b/src/types.h @@ -12,6 +12,7 @@ typedef struct MVMAsyncTaskOps MVMAsyncTaskOps; typedef struct MVMAttributeIdentifier MVMAttributeIdentifier; typedef struct MVMBoolificationSpec MVMBoolificationSpec; typedef struct MVMBootTypes MVMBootTypes; +typedef struct MVMEventSubscriptions MVMEventSubscriptions; typedef struct MVMBytecodeAnnotation MVMBytecodeAnnotation; typedef struct MVMCallCapture MVMCallCapture; typedef struct MVMCallCaptureBody MVMCallCaptureBody;