dotnet plugin: Export "dispatch_values" to the .Net code.
[collectd.git] / src / dotnet.c
1 /**
2  * collectd - src/dotnet.c
3  * Copyright (C) 2010  Florian Forster
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Florian Forster <ff at octo.it>
25  **/
26
27 #include "collectd.h"
28 #include "plugin.h"
29 #include "common.h"
30 #include "configfile.h"
31
32 #include <glib.h>
33 #include <mono/jit/jit.h>
34 #include <mono/metadata/assembly.h>
35 #include <mono/metadata/threads.h>
36
37 struct dotnet_callback_info_s
38 {
39   gint32 domain_id;
40   guint32 obj_handle;
41 };
42 typedef struct dotnet_callback_info_s dotnet_callback_info_t;
43
44 static MonoDomain *_domain = NULL;
45
46 static int dotnet_read (user_data_t *ud) /* {{{ */
47 {
48   dotnet_callback_info_t *ci = ud->data;
49   MonoDomain *domain;
50   MonoThread *thread;
51   MonoClass  *class;
52   MonoObject *object;
53   MonoMethod *method;
54   MonoObject *ret;
55
56   DEBUG ("dotnet plugin: mono_domain_get_by_id (%"PRIi32") ...",
57       (int32_t) ci->domain_id);
58   domain = mono_domain_get_by_id (ci->domain_id);
59   if (domain == NULL)
60   {
61     ERROR ("dotnet plugin: mono_domain_get_by_id failed.");
62     return (-1);
63   }
64
65   thread = mono_thread_attach (domain);
66   if (thread == NULL)
67   {
68     ERROR ("dotnet plugin: mono_thread_attach failed.");
69     return (-1);
70   }
71
72   if (!mono_domain_set (domain, /* force = */ 0))
73   {
74     ERROR ("dotnet plugin: mono_domain_set failed.");
75     return (-1);
76   }
77
78   DEBUG ("dotnet plugin: mono_gchandle_get_target ...");
79   object = mono_gchandle_get_target (ci->obj_handle);
80   if (object == NULL)
81   {
82     ERROR ("dotnet plugin: mono_gchandle_get_target failed.");
83     mono_thread_detach (thread);
84     return (-1);
85   }
86
87   DEBUG ("dotnet plugin: mono_object_get_class ...");
88   class = mono_object_get_class (object);
89   if (class == NULL)
90   {
91     ERROR ("dotnet plugin: mono_object_get_class failed.");
92     mono_thread_detach (thread);
93     return (-1);
94   }
95
96   DEBUG ("dotnet plugin: mono_class_get_method_from_name ...");
97   method = mono_class_get_method_from_name (class,
98       /* name = */ "read", /* nargs = */ 0);
99   if (method == NULL)
100   {
101     ERROR ("dotnet plugin: mono_class_get_method_from_name failed.");
102     mono_thread_detach (thread);
103     return (-1);
104   }
105
106   DEBUG ("dotnet plugin: mono_runtime_invoke ...");
107   ret = mono_runtime_invoke (method, object, /* params = */ NULL,
108       /* exception ptr = */ NULL);
109   DEBUG ("dotnet plugin: mono_runtime_invoke returned %p.", (void *) ret);
110
111   mono_thread_detach (thread);
112   return (0);
113 } /* }}} int dotnet_read */
114
115 static void dotnet_callback_info_free (void *ptr) /* {{{ */
116 {
117   dotnet_callback_info_t *ci = ptr;
118
119   if (ci == NULL)
120     return;
121
122   mono_gchandle_free (ci->obj_handle);
123   sfree (ci);
124 } /* }}} void dotnet_callback_info_free */
125
126 static MonoMethod *dotnet_object_method_get_from_name (MonoObject *object, /* {{{ */
127     const char *name, int param_count)
128 {
129   MonoClass *class;
130
131   class = mono_object_get_class (object);
132   if (class == NULL)
133     return (NULL);
134
135   while (42)
136   {
137     MonoClass *pclass;
138     MonoMethod *method;
139
140     method = mono_class_get_method_from_name (class, name, param_count);
141     if (method != NULL)
142       return (method);
143
144     pclass = mono_class_get_parent (class);
145     if ((pclass == NULL) || (pclass == class))
146     {
147       ERROR ("dotnet plugin: Unable to find method \"%s\" in class \"%s\".",
148           name, mono_class_get_name (mono_object_get_class (object)));
149       return (NULL);
150     }
151     class = pclass;
152   } /* while (42) */
153   /* Not reached */
154   return (NULL);
155 } /* }}} MonoMethod *dotnet_object_method_get_from_name */
156
157 static MonoProperty *dotnet_object_get_property_from_name (MonoObject *object, /* {{{ */
158     const char *name)
159 {
160   MonoClass *class;
161
162   class = mono_object_get_class (object);
163
164   while (42)
165   {
166     MonoClass *pclass;
167     MonoProperty *prop;
168
169     prop = mono_class_get_property_from_name (class, name);
170     if (prop != NULL)
171       return (prop);
172
173     pclass = mono_class_get_parent (class);
174     if ((pclass == NULL) || (pclass == class))
175     {
176       ERROR ("dotnet plugin: Unable to find property \"%s\" in class \"%s\".",
177           name, mono_class_get_name (mono_object_get_class (object)));
178       return (NULL);
179     }
180     class = pclass;
181   } /* while (42) */
182   /* Not reached */
183   return (NULL);
184 } /* }}} MonoProperty *dotnet_object_get_property_from_name */
185
186 static int dotnet_object_method_get_string (MonoObject *obj, /* {{{ */
187     const char *method_name, char *buffer, size_t buffer_size)
188 {
189   MonoMethod *method;
190   MonoObject *ret;
191   char *tmp;
192
193   method = dotnet_object_method_get_from_name (obj, method_name, /* nargs = */ 0);
194   if (method == NULL)
195     return (-1);
196
197   ret = mono_runtime_invoke (method, obj, /* params = */ NULL, /* exception = */ NULL);
198   if (ret == NULL)
199     return (-2);
200
201   tmp = mono_string_to_utf8 ((MonoString *) ret);
202   if (tmp == NULL)
203   {
204     ERROR ("dotnet plugin: mono_string_to_utf8 failed.");
205     return (-3);
206   }
207   DEBUG ("dotnet plugin: Method \"%s\" returned string \"%s\".",
208       mono_method_get_name (method), tmp);
209
210   sstrncpy (buffer, tmp, buffer_size);
211   return (0);
212 } /* }}} int dotnet_object_method_get_string */
213
214 static int dotnet_object_method_get_cdtime (MonoObject *obj, /* {{{ */
215     const char *method_name, cdtime_t *ret_value)
216 {
217   MonoMethod *method;
218   MonoObject *ret_obj;
219   double tmp;
220
221   method = dotnet_object_method_get_from_name (obj, method_name, /* nargs = */ 0);
222   if (method == NULL)
223     return (-1);
224
225   ret_obj = mono_runtime_invoke (method, obj, /* params = */ NULL, /* exception = */ NULL);
226   if (ret_obj == NULL)
227     return (-2);
228
229   tmp = *((double *) mono_object_unbox (ret_obj));
230   DEBUG ("dotnet plugin: Method \"%s\" returned value %.3f.",
231       mono_method_get_name (method), tmp);
232   *ret_value = DOUBLE_TO_CDTIME_T (tmp);
233
234   return (0);
235 } /* }}} int dotnet_object_method_get_cdtime */
236
237 static int dotnet_object_method_get_values (MonoObject *obj, /* {{{ */
238     value_list_t *vl)
239 {
240   MonoMethod *method;
241   MonoObject *ilist_obj;
242   MonoObject *tmp_obj;
243   MonoProperty *count_prop;
244   MonoProperty *item_prop;
245   int32_t i;
246
247   method = dotnet_object_method_get_from_name (obj, "getValues", /* nargs = */ 0);
248   if (method == NULL)
249     return (-1);
250
251   ilist_obj = mono_runtime_invoke (method, obj, /* params = */ NULL, /* exception = */ NULL);
252   if (ilist_obj == NULL)
253     return (-2);
254
255   count_prop = dotnet_object_get_property_from_name (ilist_obj, "Count");
256   if (count_prop == NULL)
257   {
258     ERROR ("dotnet plugin: dotnet_object_get_property_from_name (\"Count\") failed.");
259     return (-4);
260   }
261
262   item_prop = dotnet_object_get_property_from_name (ilist_obj, "Item");
263   if (item_prop == NULL)
264   {
265     ERROR ("dotnet plugin: dotnet_object_get_property_from_name (\"Item\") failed.");
266     return (-5);
267   }
268
269   method = mono_property_get_get_method (count_prop);
270   tmp_obj = mono_runtime_invoke (method, ilist_obj, /* params = */ NULL, /* exception = */ NULL);
271   if (tmp_obj == NULL)
272   {
273     ERROR ("dotnet plugin: mono_runtime_invoke failed.");
274     return (-6);
275   }
276   DEBUG ("dotnet plugin: Type of the Count property is \"%s\".",
277       mono_class_get_name (mono_object_get_class (tmp_obj)));
278
279   vl->values_len = (int) *((int32_t *) mono_object_unbox (tmp_obj));
280   DEBUG ("dotnet plugin: vl->values_len = %i;", vl->values_len);
281
282   if (vl->values_len <= 0)
283   {
284     vl->values_len = 0;
285     return (-7);
286   }
287
288   vl->values = calloc ((size_t) vl->values_len, sizeof (*vl->values));
289   if (vl->values == NULL)
290   {
291     vl->values_len = 0;
292     return (-8);
293   }
294
295   for (i = 0; i < ((int32_t) vl->values_len); i++)
296   {
297     void *args[2] = { &i, NULL };
298     MonoObject *value_obj;
299     const char *class_name;
300
301     method = mono_property_get_get_method (item_prop);
302     value_obj = mono_runtime_invoke (method, ilist_obj, /* params = */ args, /* exception = */ NULL);
303     if (value_obj == NULL)
304     {
305       ERROR ("dotnet plugin: Item(%i) failed.", i);
306       return (-7);
307     }
308
309     class_name = mono_class_get_name (mono_object_get_class (value_obj));
310     if (strcmp ("gaugeValue", class_name) == 0)
311     {
312       MonoObject *gauge_obj;
313
314       method = dotnet_object_method_get_from_name (value_obj, "toDouble", /* nargs = */ 0);
315       if (method == NULL)
316         return (-9);
317
318       gauge_obj = mono_runtime_invoke (method, value_obj, /* args = */ NULL, /* exception = */ NULL);
319       if (gauge_obj == NULL)
320         return (-10);
321
322       vl->values[i].gauge = (gauge_t) *((double *) mono_object_unbox (gauge_obj));
323       DEBUG ("dotnet plugin: Gauge value %"PRIi32" = %g;",
324           i, vl->values[i].gauge);
325     }
326     else if (strcmp ("deriveValue", class_name) == 0)
327     {
328       MonoObject *derive_obj;
329
330       method = dotnet_object_method_get_from_name (value_obj, "toLong", /* nargs = */ 0);
331       if (method == NULL)
332         return (-9);
333
334       derive_obj = mono_runtime_invoke (method, value_obj, /* args = */ NULL, /* exception = */ NULL);
335       if (derive_obj == NULL)
336         return (-10);
337
338       vl->values[i].derive = (derive_t) *((int64_t *) mono_object_unbox (derive_obj));
339       DEBUG ("dotnet plugin: Derive value %"PRIi32" = %"PRIi64";",
340           i, vl->values[i].derive);
341     }
342   }
343
344   return (0);
345 } /* }}} int dotnet_object_method_get_values */
346
347 /*
348  * Functions exposed to .Net
349  */
350 static int dotnet_log (int severity, MonoString *message) /* {{{ */
351 {
352   char *tmp = mono_string_to_utf8 (message);
353
354   DEBUG ("dotnet_log (severity = %i, message = \"%s\");", severity, tmp);
355
356   return (0);
357 } /* }}} int dotnet_log */
358
359 static int dotnet_register_read (MonoString *name, MonoObject *obj) /* {{{ */
360 {
361   user_data_t ud;
362   dotnet_callback_info_t *ci;
363
364   MonoClass *class;
365   MonoMethod *method;
366
367   /* Sanity checks: Make sure this object actually has the required method. */
368   class = mono_object_get_class (obj);
369   if (class == NULL)
370   {
371     ERROR ("dotnet plugin: mono_object_get_class failed.");
372     return (-1);
373   }
374
375   method = mono_class_get_method_from_name (class,
376       /* name = */ "read", /* param count = */ 0);
377   if (method == NULL)
378   {
379     ERROR ("dotnet plugin: mono_class_get_method_from_name failed.");
380     return (-1);
381   }
382
383   ci = malloc (sizeof (*ci));
384   if (ci == NULL)
385   {
386     ERROR ("dotnet plugin: malloc failed.");
387     return (-1);
388   }
389   memset (ci, 0, sizeof (*ci));
390
391   ci->domain_id = mono_domain_get_id (mono_domain_get ());
392   ci->obj_handle = mono_gchandle_new (obj, /* pinned = */ 1);
393
394   ud.data = ci;
395   ud.free_func = dotnet_callback_info_free;
396
397   plugin_register_complex_read (/* group = */ "dotnet",
398       /* name      = */ mono_string_to_utf8 (name),
399       /* callback  = */ dotnet_read,
400       /* interval  = */ NULL,
401       /* user data = */ &ud);
402
403   return (0);
404 } /* }}} int dotnet_register_read */
405
406 static int dotnet_dispatch_values (MonoObject *obj) /* {{{ */
407 {
408   value_list_t vl = VALUE_LIST_INIT;
409   int status;
410
411   status = dotnet_object_method_get_string (obj, "getHost", vl.host, sizeof (vl.host));
412   if (status != 0)
413     return (status);
414
415   status = dotnet_object_method_get_string (obj, "getPlugin", vl.plugin, sizeof (vl.plugin));
416   if (status != 0)
417     return (status);
418
419   status = dotnet_object_method_get_string (obj, "getPluginInstance", vl.plugin_instance, sizeof (vl.plugin_instance));
420   if (status != 0)
421     return (status);
422
423   status = dotnet_object_method_get_string (obj, "getType", vl.type, sizeof (vl.type));
424   if (status != 0)
425     return (status);
426
427   status = dotnet_object_method_get_string (obj, "getTypeInstance", vl.type_instance, sizeof (vl.type_instance));
428   if (status != 0)
429     return (status);
430
431   status = dotnet_object_method_get_cdtime (obj, "getInterval", &vl.interval);
432   if (status != 0)
433     return (status);
434
435   status = dotnet_object_method_get_cdtime (obj, "getTime", &vl.time);
436   if (status != 0)
437     return (status);
438
439   status = dotnet_object_method_get_values (obj, &vl);
440   if (status != 0)
441     return (status);
442
443   status = plugin_dispatch_values (&vl);
444   sfree (vl.values);
445
446   return (status);
447 } /* }}} int dotnet_dispatch_values */
448
449 /*
450  * Initialization functions
451  */
452 static int dotnet_load_class (const char *assembly_name, /* {{{ */
453     const char *name_space, const char *class_name)
454 {
455   MonoDomain *domain;
456   MonoThread *thread;
457   MonoAssembly *assembly;
458   MonoImage *image;
459   MonoClass *class;
460   MonoObject *obj;
461
462   domain = mono_domain_create ();
463   if (domain == NULL)
464   {
465     ERROR ("dotnet plugin: mono_domain_create failed.");
466     return (-1);
467   }
468
469   thread = mono_thread_attach (domain);
470   if (thread == NULL)
471   {
472     ERROR ("dotnet plugin: mono_thread_attach failed.");
473     mono_domain_free (domain, /* force = */ 0);
474     return (-1);
475   }
476
477   if (!mono_domain_set (domain, /* force = */ 0))
478   {
479     ERROR ("dotnet plugin: mono_domain_set failed.");
480     mono_thread_detach (thread);
481     mono_domain_free (domain, /* force = */ 0);
482     return (-1);
483   }
484
485   assembly = mono_domain_assembly_open (domain, assembly_name);
486   if (assembly == NULL)
487   {
488     ERROR ("dotnet plugin: mono_domain_assembly_open (\"%s\") failed.",
489       assembly_name);
490     mono_thread_detach (thread);
491     mono_domain_free (domain, /* force = */ 0);
492     return (-1);
493   }
494
495   image = mono_assembly_get_image (assembly);
496   if (image == NULL)
497   {
498     ERROR ("dotnet plugin: mono_assembly_get_image failed.");
499     mono_thread_detach (thread);
500     mono_domain_free (domain, /* force = */ 0);
501     return (-1);
502   }
503
504   class = mono_class_from_name (image,
505       (name_space != NULL) ? name_space : "",
506       class_name);
507   if (class == NULL)
508   {
509     ERROR ("dotnet plugin: Looking up class \"%s\" in assembly \"%s\" failed.",
510         class_name, assembly_name);
511     mono_thread_detach (thread);
512     mono_domain_free (domain, /* force = */ 0);
513     return (-1);
514   }
515
516   obj = mono_object_new (domain, class);
517   if (obj == NULL)
518   {
519     ERROR ("dotnet plugin: Creating a \"%s\" object failed.", class_name);
520     mono_thread_detach (thread);
521     mono_domain_free (domain, /* force = */ 0);
522     return (-1);
523   }
524
525   mono_runtime_object_init (obj);
526
527   DEBUG ("dotnet plugin: Successfully created a \"%s\" object.", class_name);
528
529   mono_thread_detach (thread);
530   return (0);
531 } /* }}} int dotnet_load_class */
532
533 /*
534  * <Plugin dotnet>
535  *   <LoadPlugin "Foobar">
536  *     NameSpace "MyCompany"
537  *     Assembly "path/to/file.dll"
538  *   </LoadPlugin>
539  *
540  *   <Plugin "Foobar">
541  *     ...
542  *   </Plugin>
543  * </Plugin>
544  */
545 static int dotnet_config_loadplugin (oconfig_item_t *ci) /* {{{ */
546 {
547   char *class_name = NULL;
548   char *name_space = NULL;
549   char *assembly_name = NULL;
550   int status;
551   int i;
552
553   status = cf_util_get_string (ci, &class_name);
554   if (status != 0)
555     return (status);
556   assert (class_name != NULL);
557
558   for (i = 0; i < ci->children_num; i++)
559   {
560     oconfig_item_t *child = ci->children + i;
561
562     if (strcasecmp ("NameSpace", child->key) == 0)
563       cf_util_get_string (child, &name_space);
564     else if (strcasecmp ("Assembly", child->key) == 0)
565       cf_util_get_string (child, &assembly_name);
566     else
567       WARNING ("dotnet plugin: Config option \"%s\" is not allowed here.",
568           child->key);
569   }
570
571   if (assembly_name == NULL)
572   {
573     ERROR ("dotnet plugin: No \"Assembly\" option within this \"LoadPlugin\" "
574         "block (class \"%s\").", class_name);
575     sfree (class_name);
576     sfree (name_space);
577   }
578
579   status = dotnet_load_class (assembly_name, name_space, class_name);
580   if (status != 0)
581     return (status);
582
583   return (0);
584 } /* }}} int dotnet_config_loadplugin */
585
586 static int dotnet_config (oconfig_item_t *ci) /* {{{ */
587 {
588   int i;
589
590   for (i = 0; i < ci->children_num; i++)
591   {
592     oconfig_item_t *child = ci->children + i;
593
594     if (strcasecmp ("LoadPlugin", child->key) == 0)
595       dotnet_config_loadplugin (child);
596     else
597       WARNING ("dotnet plugin: Ignoring unknown config option \"%s\".",
598           child->key);
599   }
600
601   return (0);
602 } /* }}} int dotnet_config */
603
604 void module_register (void)
605 {
606   _domain = mono_jit_init (PACKAGE_NAME);
607   if (_domain == NULL)
608   {
609     ERROR ("dotnet plugin: mono_jit_init failed.");
610     return;
611   }
612
613   mono_add_internal_call ("CollectdAPI.Collectd::log", dotnet_log);
614   mono_add_internal_call ("CollectdAPI.Collectd::registerRead", dotnet_register_read);
615   mono_add_internal_call ("CollectdAPI.Collectd::dispatchValues", dotnet_dispatch_values);
616
617   plugin_register_complex_config ("dotnet", dotnet_config);
618 } /* void module_register */
619
620 /* vim: set sw=2 sts=2 et fdm=marker : */