root/src/modules/http.c

Revision 64e4b06c954e1727bf19b222f32cea893b5cc4c1, 19.7 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 6 years ago)

load and parse resmon output

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  */
5
6 #include "noit_defines.h"
7
8 #include <stdio.h>
9 #include <unistd.h>
10 #include <errno.h>
11 #include <assert.h>
12 #include <math.h>
13
14 #include <libxml/parser.h>
15 #include <libxml/tree.h>
16 #include <libxml/xpath.h>
17
18 #include "noit_module.h"
19 #include "noit_poller.h"
20 #include "utils/noit_log.h"
21 #include "utils/noit_hash.h"
22
23 #include <apr_uri.h>
24 #include <apr_atomic.h>
25 #include <apr_strings.h>
26 #include "serf.h"
27
28 #define NOIT_HTTP_VERSION_STRING "0.1"
29
30 typedef struct {
31   noit_hash_table *options;
32   void (*results)(noit_module_t *, noit_check_t);
33 } serf_module_conf_t;
34
35 typedef struct {
36   int using_ssl;
37   serf_ssl_context_t *ssl_ctx;
38   serf_bucket_alloc_t *bkt_alloc;
39 } app_baton_t;
40
41 typedef struct {
42   serf_response_acceptor_t acceptor;
43   app_baton_t *acceptor_baton;
44
45   serf_response_handler_t handler;
46   const char *host;
47   const char *method;
48   const char *path;
49   const char *authn;
50
51   noit_module_t *self;
52   noit_check_t check;
53 } handler_baton_t;
54
55 typedef struct buf_t {
56   char *b;
57   int l;
58 } buf_t;
59
60 typedef struct {
61   apr_pool_t *pool;
62   apr_sockaddr_t *address;
63   serf_context_t *context;
64   serf_connection_t *connection;
65   serf_request_t *request;
66   app_baton_t app_ctx;
67   handler_baton_t handler_ctx;
68   apr_uri_t url;
69   int timed_out;
70
71   serf_status_line status;
72   buf_t headers;
73   buf_t body;
74
75   struct timeval finish_time;
76   eventer_t fd_event;
77   eventer_t timeout_event;
78 } check_info_t;
79
80 typedef struct {
81   noit_module_t *self;
82   noit_check_t check;
83   void *serf_baton;
84   apr_socket_t *skt;
85 } serf_closure_t;
86
87 static noit_log_stream_t nlerr = NULL;
88 static noit_log_stream_t nldeb = NULL;
89 static int serf_handler(eventer_t e, int mask, void *closure,
90                         struct timeval *now);
91 static int serf_recur_handler(eventer_t e, int mask, void *closure,
92                               struct timeval *now);
93 static void serf_log_results(noit_module_t *self, noit_check_t check);
94 static void resmon_log_results(noit_module_t *self, noit_check_t check);
95
96 static int serf_config(noit_module_t *self, noit_hash_table *options) {
97   serf_module_conf_t *conf;
98   conf = calloc(1, sizeof(*conf));
99   conf->options = options;
100   conf->results = serf_log_results;
101   noit_module_set_userdata(self, conf);
102   return 0;
103 }
104 static int resmon_config(noit_module_t *self, noit_hash_table *options) {
105   serf_module_conf_t *conf;
106   conf = calloc(1, sizeof(*conf));
107   conf->options = options;
108   if(!conf->options) conf->options = calloc(1, sizeof(*conf->options));
109   noit_hash_store(conf->options, strdup("url"), strlen("url"),
110                   strdup("http://localhost:81/"));
111   conf->results = resmon_log_results;
112   noit_module_set_userdata(self, conf);
113   return 0;
114 }
115 static void generic_log_results(noit_module_t *self, noit_check_t check) {
116   serf_module_conf_t *module_conf;
117   module_conf = noit_module_get_userdata(self);
118   module_conf->results(self, check);
119 }
120 static void serf_log_results(noit_module_t *self, noit_check_t check) {
121   check_info_t *ci = check->closure;
122   struct timeval duration;
123   stats_t current;
124   int expect_code = 200;
125   char *code_str;
126   char human_buffer[256], code[4], rt[14];
127
128   if(noit_hash_retrieve(check->config, "code", strlen("code"),
129                         (void **)&code_str))
130     expect_code = atoi(code_str);
131
132   sub_timeval(ci->finish_time, check->last_fire_time, &duration);
133
134   snprintf(code, sizeof(code), "%3d", ci->status.code);
135   snprintf(rt, sizeof(rt), "%.3fms",
136            (float)duration.tv_sec + (float)duration.tv_usec / 1000000.0);
137   snprintf(human_buffer, sizeof(human_buffer),
138            "code=%s,rt=%s,bytes=%d",
139            ci->status.code ? code : "undefined",
140            ci->timed_out ? "timeout" : rt,
141            ci->body.l);
142   noitL(nldeb, "http(%s) [%s]\n", check->target, human_buffer);
143
144   current.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
145   current.available = (ci->timed_out || !ci->status.code) ? NP_UNAVAILABLE : NP_AVAILABLE;
146   current.state = (ci->status.code != 200) ? NP_BAD : NP_GOOD;
147   current.status = human_buffer;
148   noit_poller_set_state(check, &current);
149 }
150 static void resmon_log_results(noit_module_t *self, noit_check_t check) {
151   check_info_t *ci = check->closure;
152   struct timeval duration;
153   stats_t current;
154   int expect_code = 200;
155   int services = 0;
156   char *code_str;
157   char human_buffer[256], code[4], rt[14];
158   xmlDocPtr resmon_results = NULL;
159   xmlXPathContextPtr xpath_ctxt = NULL;
160
161   if(ci->body.b) resmon_results = xmlParseMemory(ci->body.b, ci->body.l);
162   if(resmon_results) {
163     xmlXPathObjectPtr pobj;
164     xpath_ctxt = xmlXPathNewContext(resmon_results);
165     pobj = xmlXPathEval((xmlChar *)"/ResmonResults/ResmonResult", xpath_ctxt);
166     if(pobj)
167       if(pobj->type == XPATH_NODESET)
168         services = xmlXPathNodeSetGetLength(pobj->nodesetval);
169     xmlXPathFreeObject(pobj);
170   } else {
171     noitL(nlerr, "Error in resmon doc: %s\n", ci->body.b);
172   }
173   if(xpath_ctxt) xmlXPathFreeContext(xpath_ctxt);
174   if(resmon_results) xmlFreeDoc(resmon_results);
175
176   sub_timeval(ci->finish_time, check->last_fire_time, &duration);
177
178   snprintf(rt, sizeof(rt), "%.3fms",
179            (float)duration.tv_sec + (float)duration.tv_usec / 1000000.0);
180   snprintf(human_buffer, sizeof(human_buffer),
181            "services=%d,rt=%s",
182            services,
183            ci->timed_out ? "timeout" : rt);
184   noitL(nldeb, "resmon(%s) [%s]\n", check->target, human_buffer);
185
186   current.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
187   current.available = (ci->timed_out || ci->status.code != 200) ?
188                           NP_UNAVAILABLE : NP_AVAILABLE;
189   current.state = services ? NP_GOOD : NP_BAD;
190   current.status = human_buffer;
191   noit_poller_set_state(check, &current);
192 }
193 static int serf_complete(eventer_t e, int mask,
194                          void *closure, struct timeval *now) {
195   serf_closure_t *ccl = (serf_closure_t *)closure;
196   check_info_t *ci = (check_info_t *)ccl->check->closure;
197
198   noitLT(nldeb, now, "serf_complete(%s)\n", ccl->check->target);
199   generic_log_results(ccl->self, ccl->check);
200   if(ci->connection) {
201     serf_connection_close(ci->connection);
202     ci->connection = NULL;
203   }
204   if(ci->fd_event) {
205     eventer_remove_fd(ci->fd_event->fd);
206     eventer_free(ci->fd_event);
207     ci->fd_event = NULL;
208   }
209   ci->timeout_event = NULL;
210   apr_pool_destroy(ci->pool);
211   memset(ci, 0, sizeof(*ci));
212   ccl->check->flags &= ~NP_RUNNING;
213   free(ccl);
214   return 0;
215 }
216
217 static int serf_handler(eventer_t e, int mask,
218                         void *closure, struct timeval *now) {
219   apr_pollfd_t desc = { 0 };
220   serf_closure_t *sct = closure;
221   check_info_t *ci = sct->check->closure;
222
223   desc.desc_type = APR_POLL_SOCKET;
224   desc.desc.s = sct->skt;
225
226   desc.rtnevents = 0;
227   if(mask & EVENTER_READ) desc.rtnevents |= APR_POLLIN;
228   if(mask & EVENTER_WRITE) desc.rtnevents |= APR_POLLOUT;
229   if(mask & EVENTER_EXCEPTION) desc.rtnevents |= APR_POLLERR;
230   serf_event_trigger(ci->context, sct->serf_baton, &desc);
231   serf_context_prerun(ci->context);
232
233   /* We're about to deschedule and free the event, drop our reference */
234   if(!e->mask)
235     ci->fd_event = NULL;
236
237   return e->mask;
238 }
239
240 static int serf_init(noit_module_t *self) {
241   return 0;
242 }
243 static void closed_connection(serf_connection_t *conn,
244                               void *closed_baton,
245                               apr_status_t why,
246                               apr_pool_t *pool) {
247 }
248
249 static serf_bucket_t* conn_setup(apr_socket_t *skt,
250                                 void *setup_baton,
251                                 apr_pool_t *pool) {
252   serf_bucket_t *c;
253   app_baton_t *ctx = setup_baton;
254
255   c = serf_bucket_socket_create(skt, ctx->bkt_alloc);
256   if (ctx->using_ssl) {
257       c = serf_bucket_ssl_decrypt_create(c, ctx->ssl_ctx, ctx->bkt_alloc);
258       if (!ctx->ssl_ctx) {
259           ctx->ssl_ctx = serf_bucket_ssl_decrypt_context_get(c);
260       }
261   }
262
263   return c;
264 }
265
266 static serf_bucket_t* accept_response(serf_request_t *request,
267                                       serf_bucket_t *stream,
268                                       void *acceptor_baton,
269                                       apr_pool_t *pool) {
270   serf_bucket_t *c;
271   serf_bucket_alloc_t *bkt_alloc;
272
273   /* get the per-request bucket allocator */
274   bkt_alloc = serf_request_get_alloc(request);
275
276   /* Create a barrier so the response doesn't eat us! */
277   c = serf_bucket_barrier_create(stream, bkt_alloc);
278
279   return serf_bucket_response_create(c, bkt_alloc);
280 }
281
282 static void append_buf(apr_pool_t *p, buf_t *b,
283                        const char *data, int len) {
284   char *n;
285   n = apr_palloc(p, b->l + len + 1);
286   if(b->l == 0)
287     b->b = n;
288   else {
289     memcpy(n, b->b, b->l);
290     b->b = n;
291   }
292   memcpy(b->b + b->l, data, len);
293   b->l += len;
294   b->b[b->l] = '\0';
295 }
296
297 static apr_status_t handle_response(serf_request_t *request,
298                                     serf_bucket_t *response,
299                                     void *handler_baton,
300                                     apr_pool_t *pool) {
301   const char *data;
302   apr_size_t len;
303   apr_status_t status;
304   handler_baton_t *ctx = handler_baton;
305   check_info_t *ci = ctx->check->closure;
306
307   if(response == NULL) {
308     /* We were cancelled. */
309     goto finish;
310   }
311   status = serf_bucket_response_status(response, &ci->status);
312   if (status) {
313     if (APR_STATUS_IS_EAGAIN(status)) {
314       return status;
315     }
316     goto finish;
317   }
318
319   while (1) {
320     status = serf_bucket_read(response, 1024*32, &data, &len);
321     if (SERF_BUCKET_READ_ERROR(status))
322       return status;
323
324     append_buf(ci->pool, &ci->body, data, len);
325
326     /* are we done yet? */
327     if (APR_STATUS_IS_EOF(status)) {
328       serf_bucket_t *hdrs;
329       hdrs = serf_bucket_response_get_headers(response);
330       while (1) {
331         status = serf_bucket_read(hdrs, 2048, &data, &len);
332         if (SERF_BUCKET_READ_ERROR(status))
333           return status;
334
335         append_buf(ci->pool, &ci->headers, data, len);
336         if (APR_STATUS_IS_EOF(status)) {
337           break;
338         }
339       }
340
341       goto finish;
342     }
343
344     /* have we drained the response so far? */
345     if (APR_STATUS_IS_EAGAIN(status))
346       return status;
347
348     /* loop to read some more. */
349   }
350  finish:
351   gettimeofday(&ci->finish_time, NULL);
352   if(ci->timeout_event) {
353     eventer_remove(ci->timeout_event);
354     ci->timed_out = 0;
355     memcpy(&ci->timeout_event->whence, &ci->finish_time,
356            sizeof(&ci->finish_time));
357     eventer_add(ci->timeout_event);
358   }
359   return APR_EOF;
360 }
361
362 static apr_status_t setup_request(serf_request_t *request,
363                                   void *setup_baton,
364                                   serf_bucket_t **req_bkt,
365                                   serf_response_acceptor_t *acceptor,                                             void **acceptor_baton,
366                                   serf_response_handler_t *handler,
367                                   void **handler_baton,
368                                   apr_pool_t *pool) {
369   handler_baton_t *ctx = setup_baton;
370   serf_bucket_t *hdrs_bkt;
371   serf_bucket_t *body_bkt;
372
373   body_bkt = NULL;
374
375   *req_bkt = serf_bucket_request_create(ctx->method, ctx->path, body_bkt,
376                                         serf_request_get_alloc(request));
377
378   hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
379
380   serf_bucket_headers_setn(hdrs_bkt, "Host", ctx->host);
381   serf_bucket_headers_setn(hdrs_bkt, "User-Agent",
382                            "Noit/" NOIT_HTTP_VERSION_STRING);
383   /* Shouldn't serf do this for us? */
384   serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip");
385
386   if (ctx->authn != NULL) {
387     serf_bucket_headers_setn(hdrs_bkt, "Authorization", ctx->authn);
388   }
389
390   if (ctx->acceptor_baton->using_ssl) {
391     serf_bucket_alloc_t *req_alloc;
392     app_baton_t *app_ctx = ctx->acceptor_baton;
393
394     req_alloc = serf_request_get_alloc(request);
395
396     if (app_ctx->ssl_ctx == NULL) {
397       *req_bkt =
398         serf_bucket_ssl_encrypt_create(*req_bkt, NULL,
399                                        app_ctx->bkt_alloc);
400       app_ctx->ssl_ctx =
401         serf_bucket_ssl_encrypt_context_get(*req_bkt);
402     }
403     else {
404       *req_bkt =
405         serf_bucket_ssl_encrypt_create(*req_bkt, app_ctx->ssl_ctx,
406                                        app_ctx->bkt_alloc);
407     }
408   }
409
410   *acceptor = ctx->acceptor;
411   *acceptor_baton = ctx->acceptor_baton;
412   *handler = ctx->handler;
413   *handler_baton = ctx;
414
415   return APR_SUCCESS;
416 }
417
418 struct __unix_apr_socket_t {
419   apr_pool_t *pool;
420   int socketdes;
421 };
422
423 static apr_status_t serf_eventer_add(void *user_baton,
424                                      apr_pollfd_t *pfd,
425                                      void *serf_baton) {
426   eventer_t e, newe = NULL;
427   serf_closure_t *sct = user_baton, *newsct;
428   assert(pfd->desc_type == APR_POLL_SOCKET);
429   struct __unix_apr_socket_t *hack = (struct __unix_apr_socket_t *)pfd->desc.s;
430
431   noitL(nldeb, "serf_eventer_add() => %d\n", hack->socketdes);
432   e = eventer_find_fd(hack->socketdes);
433   if(!e) {
434     newe = e = eventer_alloc();
435     e->fd = hack->socketdes;
436     e->callback = serf_handler;
437     e->closure = calloc(1, sizeof(serf_closure_t));
438   }
439   newsct = e->closure;
440   newsct->self = sct->self;
441   newsct->check = sct->check;
442   newsct->serf_baton = serf_baton;
443   newsct->skt = pfd->desc.s;
444   e->mask = 0;
445   if(pfd->reqevents & APR_POLLIN) e->mask |= EVENTER_READ;
446   if(pfd->reqevents & APR_POLLOUT) e->mask |= EVENTER_WRITE;
447   if(pfd->reqevents & APR_POLLERR) e->mask |= EVENTER_EXCEPTION;
448   if(newe) {
449     check_info_t *ci = sct->check->closure;
450     eventer_add(newe);
451     ci->fd_event = newe;
452   }
453 /* ** Unneeded as this is called recursively **
454   else
455     eventer_update(e);
456 */
457   return APR_SUCCESS;
458 }
459 static apr_status_t serf_eventer_remove(void *user_baton,
460                                         apr_pollfd_t *pfd,
461                                         void *serf_baton) {
462   serf_closure_t *sct = user_baton;
463   check_info_t *ci;
464   eventer_t e;
465
466   ci = sct->check->closure;
467   assert(pfd->desc_type == APR_POLL_SOCKET);
468   struct __unix_apr_socket_t *hack = (struct __unix_apr_socket_t *)pfd->desc.s;
469
470   noitL(nldeb, "serf_eventer_remove() => %d\n", hack->socketdes);
471   e = eventer_find_fd(hack->socketdes);
472   if(e) e->mask = 0;
473   return 0;
474 }
475
476 static int serf_initiate(noit_module_t *self, noit_check_t check) {
477   serf_closure_t *ccl;
478   check_info_t *ci;
479   struct timeval when, p_int;
480   apr_status_t status;
481   eventer_t newe;
482   serf_module_conf_t *mod_config;
483   char *config_url;
484
485   mod_config = noit_module_get_userdata(self);
486   ci = (check_info_t *)check->closure;
487   /* We cannot be running */
488   assert(!(check->flags & NP_RUNNING));
489   check->flags |= NP_RUNNING;
490   noitL(nldeb, "serf_initiate(%p,%s)\n",
491         self, check->target);
492
493   /* remove a timeout if we still have one -- we should unless someone
494    * has set a lower timeout than the period.
495    */
496   ci->timed_out = 1;
497   if(ci->timeout_event) {
498     eventer_remove(ci->timeout_event);
499     free(ci->timeout_event->closure);
500     eventer_free(ci->timeout_event);
501     ci->timeout_event = NULL;
502   }
503   assert(!ci->pool);
504   apr_pool_create(&ci->pool, NULL);
505   apr_atomic_init(ci->pool);
506
507   gettimeofday(&when, NULL);
508   memcpy(&check->last_fire_time, &when, sizeof(when));
509
510   ccl = apr_pcalloc(ci->pool, sizeof(*ccl));
511   ccl->self = self;
512   ccl->check = check;
513
514   if(!noit_hash_retrieve(check->config, "url", strlen("url"),
515                         (void **)&config_url))
516     if(!mod_config->options ||
517        !noit_hash_retrieve(mod_config->options, "url", strlen("url"),
518                            (void **)&config_url))
519       config_url = "http://localhost/";
520   apr_uri_parse(ci->pool, config_url, &ci->url);
521
522   if (!ci->url.port) {
523     ci->url.port = apr_uri_port_of_scheme(ci->url.scheme);
524   }
525   if (!ci->url.path) {
526     ci->url.path = "/";
527   }
528
529   if (strcasecmp(ci->url.scheme, "https") == 0) {
530     ci->app_ctx.using_ssl = 1;
531   }
532   else {
533     ci->app_ctx.using_ssl = 0;
534   }
535
536   status = apr_sockaddr_info_get(&ci->address,
537                                  check->target, APR_UNSPEC, ci->url.port, 0,
538                                  ci->pool);
539   if (status) {
540     /* Handle error -- log failure */
541     apr_pool_destroy(ci->pool);
542     memset(ci, 0, sizeof(*ci));
543     check->flags &= ~NP_RUNNING;
544     return 0;
545   }
546
547   ci->context = serf_context_create_ex(ccl, serf_eventer_add,
548                                        serf_eventer_remove, ci->pool);
549
550   ci->app_ctx.bkt_alloc = serf_bucket_allocator_create(ci->pool, NULL, NULL);
551   ci->app_ctx.ssl_ctx = NULL;
552
553   ci->connection = serf_connection_create(ci->context, ci->address,
554                                           conn_setup, &ci->app_ctx,
555                                           closed_connection, &ci->app_ctx,
556                                           ci->pool);
557
558   ci->handler_ctx.method = apr_pstrdup(ci->pool, "GET");
559   ci->handler_ctx.host = apr_pstrdup(ci->pool, check->target);
560   ci->handler_ctx.path = ci->url.path;
561   ci->handler_ctx.authn = NULL;
562
563   ci->handler_ctx.acceptor = accept_response;
564   ci->handler_ctx.acceptor_baton = &ci->app_ctx;
565   ci->handler_ctx.handler = handle_response;
566   ci->handler_ctx.self = self;
567   ci->handler_ctx.check = check;
568
569   ci->request = serf_connection_request_create(ci->connection, setup_request,
570                                                &ci->handler_ctx);
571   serf_context_prerun(ci->context);
572
573   newe = eventer_alloc();
574   newe->mask = EVENTER_TIMER;
575   gettimeofday(&when, NULL);
576   p_int.tv_sec = check->timeout / 1000;
577   p_int.tv_usec = (check->timeout % 1000) * 1000;
578   add_timeval(when, p_int, &newe->whence);
579   ccl = calloc(1, sizeof(*ccl));
580   ccl->self = self;
581   ccl->check = check;
582   newe->closure = ccl;
583   newe->callback = serf_complete;
584   eventer_add(newe);
585   ci->timeout_event = newe;
586   return 0;
587 }
588 static int serf_schedule_next(noit_module_t *self,
589                               eventer_t e, noit_check_t check,
590                               struct timeval *now) {
591   eventer_t newe;
592   struct timeval last_check = { 0L, 0L };
593   struct timeval period, earliest;
594   serf_closure_t *ccl;
595
596   /* If we have an event, we know when we intended it to fire.  This means
597    * we should schedule that point + period.
598    */
599   if(now)
600     memcpy(&earliest, now, sizeof(earliest));
601   else
602     gettimeofday(&earliest, NULL);
603   if(e) memcpy(&last_check, &e->whence, sizeof(last_check));
604   period.tv_sec = check->period / 1000;
605   period.tv_usec = (check->period % 1000) * 1000;
606
607   newe = eventer_alloc();
608   memcpy(&newe->whence, &last_check, sizeof(last_check));
609   add_timeval(newe->whence, period, &newe->whence);
610   if(compare_timeval(newe->whence, earliest) < 0)
611     memcpy(&newe->whence, &earliest, sizeof(earliest));
612   newe->mask = EVENTER_TIMER;
613   newe->callback = serf_recur_handler;
614   ccl = calloc(1, sizeof(*ccl));
615   ccl->self = self;
616   ccl->check = check;
617   newe->closure = ccl;
618
619   eventer_add(newe);
620   check->fire_event = newe;
621   return 0;
622 }
623 static int serf_recur_handler(eventer_t e, int mask, void *closure,
624                               struct timeval *now) {
625   serf_closure_t *cl = (serf_closure_t *)closure;
626   serf_schedule_next(cl->self, e, cl->check, now);
627   serf_initiate(cl->self, cl->check);
628   free(cl);
629   return 0;
630 }
631 static int serf_initiate_check(noit_module_t *self, noit_check_t check) {
632   check->closure = calloc(1, sizeof(check_info_t));
633   serf_schedule_next(self, NULL, check, NULL);
634   return 0;
635 }
636
637 static int serf_onload(noit_module_t *self) {
638   apr_initialize();
639   atexit(apr_terminate);
640
641   nlerr = noit_log_stream_find("error/serf");
642   nldeb = noit_log_stream_find("debug/serf");
643   if(!nlerr) nlerr = noit_stderr;
644   if(!nldeb) nldeb = noit_debug;
645
646   eventer_name_callback("http/serf_handler", serf_handler);
647   eventer_name_callback("http/serf_complete", serf_complete);
648   eventer_name_callback("http/serf_recur_handler", serf_recur_handler);
649   return 0;
650 }
651 noit_module_t http = {
652   NOIT_MODULE_MAGIC,
653   NOIT_MODULE_ABI_VERSION,
654   "http",
655   "libserf-based HTTP and HTTPS resource checker",
656   serf_onload,
657   serf_config,
658   serf_init,
659   serf_initiate_check
660 };
661
662 noit_module_t resmon = {
663   NOIT_MODULE_MAGIC,
664   NOIT_MODULE_ABI_VERSION,
665   "resmon",
666   "libserf-based resmon resource checker",
667   serf_onload,
668   resmon_config,
669   serf_init,
670   serf_initiate_check
671 };
672
Note: See TracBrowser for help on using the browser.