Changeset 5f413b75efe299d3b88314bbcc3c21cbbcec36a7
- Timestamp:
- 02/02/08 04:08:49 (5 years ago)
- git-parent:
- Files:
-
- src/.gdbinit (added)
- src/modules/http.c (modified) (11 diffs)
- src/modules/ping_icmp.c (modified) (3 diffs)
- src/noit_conf.c (modified) (2 diffs)
- src/noit_module.h (modified) (1 diff)
- src/noit_poller.c (modified) (13 diffs)
- src/noit_poller.h (modified) (5 diffs)
- src/sample.conf (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
src/modules/http.c
r78402cd r5f413b7 81 81 serf_check_info_t serf; 82 82 struct timeval xml_doc_time; 83 char *xpathexpr; 83 84 xmlDocPtr xml_doc; 85 char *resmod; 86 char *resserv; 84 87 } resmon_check_info_t; 85 88 … … 99 102 static void serf_log_results(noit_module_t *self, noit_check_t check); 100 103 static void resmon_log_results(noit_module_t *self, noit_check_t check); 104 static void resmon_part_log_results(noit_module_t *self, noit_check_t check, 105 noit_check_t parent); 101 106 102 107 static int serf_config(noit_module_t *self, noit_hash_table *options) { … … 132 137 char human_buffer[256], code[4], rt[14]; 133 138 139 memset(¤t, 0, sizeof(current)); 140 134 141 if(noit_hash_retrieve(check->config, "code", strlen("code"), 135 142 (void **)&code_str)) … … 148 155 noitL(nldeb, "http(%s) [%s]\n", check->target, human_buffer); 149 156 157 memcpy(¤t.whence, &ci->finish_time, sizeof(current.whence)); 150 158 current.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000; 151 159 current.available = (ci->timed_out || !ci->status.code) ? NP_UNAVAILABLE : NP_AVAILABLE; 152 160 current.state = (ci->status.code != 200) ? NP_BAD : NP_GOOD; 153 161 current.status = human_buffer; 154 noit_poller_set_state(check, ¤t); 162 if(current.available == NP_AVAILABLE) { 163 noit_poller_set_metric_int(¤t, "code", &ci->status.code); 164 noit_poller_set_metric_int(¤t, "bytes", &ci->body.l); 165 } 166 else { 167 noit_poller_set_metric_int(¤t, "code", NULL); 168 noit_poller_set_metric_int(¤t, "bytes", NULL); 169 } 170 noit_poller_set_state(self, check, ¤t); 171 } 172 static void resmon_part_log_results_xml(noit_module_t *self, 173 noit_check_t check, 174 xmlDocPtr xml) { 175 resmon_check_info_t *rci = check->closure; 176 xmlXPathContextPtr xpath_ctxt = NULL; 177 stats_t current; 178 179 memset(¤t, 0, sizeof(current)); 180 current.available = NP_UNAVAILABLE; 181 current.state = NP_BAD; 182 183 if(xml && rci->xpathexpr) { 184 current.available = NP_AVAILABLE; 185 xpath_ctxt = xmlXPathNewContext(xml); 186 if(xpath_ctxt) { 187 xmlXPathObjectPtr pobj; 188 pobj = xmlXPathEval((xmlChar *)rci->xpathexpr, xpath_ctxt); 189 if(pobj) { 190 int i, cnt; 191 cnt = xmlXPathNodeSetGetLength(pobj->nodesetval); 192 for(i=0; i<cnt; i++) { 193 xmlNodePtr node; 194 char *value; 195 node = xmlXPathNodeSetItem(pobj->nodesetval, i); 196 value = (char *)xmlXPathCastNodeToString(node); 197 if(!strcmp((char *)node->name,"last_runtime_seconds")) { 198 float duration = atof(value) * 1000; 199 current.duration = (int) duration; 200 } 201 else if(!strcmp((char *)node->name, "message")) { 202 current.status = strdup(value); 203 } 204 else if(!strcmp((char *)node->name, "state")) { 205 current.state = strcmp(value,"OK") ? NP_BAD : NP_GOOD; 206 } 207 } 208 } 209 } 210 } 211 memcpy(¤t.whence, &rci->serf.finish_time, sizeof(current.whence)); 212 current.status = current.status ? current.status : strdup("unknown"); 213 noitL(nldeb, "resmon_part(%s/%s/%s) [%s]\n", check->target, 214 rci->resmod, rci->resserv, current.status); 215 noit_poller_set_state(self, check, ¤t); 216 } 217 static void resmon_part_log_results(noit_module_t *self, noit_check_t check, 218 noit_check_t parent) { 219 resmon_check_info_t *rci = parent->closure; 220 resmon_part_log_results_xml(self, check, rci->xml_doc); 155 221 } 156 222 static void resmon_log_results(noit_module_t *self, noit_check_t check) { … … 163 229 xmlDocPtr resmon_results = NULL; 164 230 xmlXPathContextPtr xpath_ctxt = NULL; 231 xmlXPathObjectPtr pobj = NULL; 232 233 memset(¤t, 0, sizeof(current)); 165 234 166 235 if(ci->body.b) resmon_results = xmlParseMemory(ci->body.b, ci->body.l); 167 236 if(resmon_results) { 168 xmlXPathObjectPtr pobj;169 237 xpath_ctxt = xmlXPathNewContext(resmon_results); 170 238 pobj = xmlXPathEval((xmlChar *)"/ResmonResults/ResmonResult", xpath_ctxt); … … 172 240 if(pobj->type == XPATH_NODESET) 173 241 services = xmlXPathNodeSetGetLength(pobj->nodesetval); 174 xmlXPathFreeObject(pobj);175 242 } else { 176 243 noitL(nlerr, "Error in resmon doc: %s\n", ci->body.b); 177 244 } 178 if(xpath_ctxt) xmlXPathFreeContext(xpath_ctxt); 179 180 /* Save out results for future dependent checks */245 246 /* Save our results for future dependent checks */ 247 memcpy(¤t.whence, &ci->finish_time, sizeof(current.whence)); 181 248 memcpy(&rci->xml_doc_time, &ci->finish_time, sizeof(ci->finish_time)); 182 249 if(rci->xml_doc) xmlFreeDoc(rci->xml_doc); 183 250 rci->xml_doc = resmon_results; 184 251 252 if(rci->xpathexpr) { 253 /* This is actually a part check... we had to do all the work as 254 * it isn't being used as a causal firing from a generic resmon check 255 */ 256 resmon_part_log_results_xml(self, check, rci->xml_doc); 257 goto out; 258 } 259 185 260 sub_timeval(ci->finish_time, check->last_fire_time, &duration); 186 187 261 snprintf(rt, sizeof(rt), "%.3fms", 188 262 (float)duration.tv_sec + (float)duration.tv_usec / 1000000.0); … … 198 272 current.state = services ? NP_GOOD : NP_BAD; 199 273 current.status = human_buffer; 200 noit_poller_set_state(check, ¤t); 274 275 noit_poller_set_metric_int(¤t, "services", &services); 276 if(services) { 277 int i; 278 for(i=0; i<services; i++) { 279 xmlNodePtr node, attrnode; 280 node = xmlXPathNodeSetItem(pobj->nodesetval, i); 281 if(node) { 282 int a; 283 char *attrs[3] = { "last_runtime_seconds", "state", "message" }; 284 char *resmod = NULL, *resserv = NULL, *value = NULL; 285 char attr[1024]; 286 xmlXPathObjectPtr sobj; 287 288 xpath_ctxt->node = node; 289 sobj = xmlXPathEval((xmlChar *)"@module", xpath_ctxt); 290 resmod = (char *)xmlXPathCastNodeSetToString(sobj->nodesetval); 291 sobj = xmlXPathEval((xmlChar *)"@service", xpath_ctxt); 292 resserv = (char *)xmlXPathCastNodeSetToString(sobj->nodesetval); 293 if(!resmod && !resserv) continue; 294 295 for(a=0; a<3; a++) { 296 int intval; 297 sobj = xmlXPathEval((xmlChar *)attrs[a], xpath_ctxt); 298 attrnode = xmlXPathNodeSetItem(sobj->nodesetval, 0); 299 value = (char *)xmlXPathCastNodeToString(attrnode); 300 snprintf(attr, sizeof(attr), "%s`%s`%s", 301 resmod, resserv, (char *)attrnode->name); 302 switch(a) { 303 case 0: 304 /* The first is integer */ 305 intval = (int)(atof(value) * 1000.0); 306 noit_poller_set_metric_int(¤t, attr, &intval); 307 break; 308 case 1: 309 case 2: 310 noit_poller_set_metric_string(¤t, attr, (char *)value); 311 break; 312 } 313 } 314 } 315 } 316 } 317 318 noit_poller_set_state(self, check, ¤t); 319 320 out: 321 if(pobj) xmlXPathFreeObject(pobj); 322 if(xpath_ctxt) xmlXPathFreeContext(xpath_ctxt); 201 323 } 202 324 static int serf_complete(eventer_t e, int mask, … … 641 763 } 642 764 static int serf_initiate_check(noit_module_t *self, noit_check_t check, 643 int once ) {765 int once, noit_check_t cause) { 644 766 if(!check->closure) check->closure = calloc(1, sizeof(serf_check_info_t)); 645 767 if(once) { … … 653 775 } 654 776 static int resmon_initiate_check(noit_module_t *self, noit_check_t check, 655 int once ) {777 int once, noit_check_t parent) { 656 778 /* resmon_check_info_t gives us a bit more space */ 657 779 if(!check->closure) check->closure = calloc(1, sizeof(resmon_check_info_t)); … … 664 786 return 0; 665 787 } 788 789 static int resmon_part_initiate_check(noit_module_t *self, noit_check_t check, 790 int once, noit_check_t parent) { 791 char xpathexpr[1024]; 792 const char *resmod, *resserv; 793 resmon_check_info_t *rci; 794 795 if(!check->closure) check->closure = calloc(1, sizeof(resmon_check_info_t)); 796 rci = check->closure; 797 if(!rci->xpathexpr) { 798 if(!noit_hash_retrieve(check->config, 799 "resmon_module", strlen("resmon_module"), 800 (void **)&resmod)) { 801 resmod = "DUMMY_MODULE"; 802 } 803 if(!noit_hash_retrieve(check->config, 804 "resmon_service", strlen("resmon_service"), 805 (void **)&resserv)) { 806 resserv = "DUMMY_SERVICE"; 807 } 808 snprintf(xpathexpr, sizeof(xpathexpr), 809 "//ResmonResult[@module=\"%s\" and @service=\"%s\"]/*", 810 resmod, resserv); 811 rci->xpathexpr = strdup(xpathexpr); 812 rci->resmod = strdup(resmod); 813 rci->resserv = strdup(resserv); 814 } 815 816 if(parent && !strcmp(parent->module, "resmon")) { 817 /* Content is cached in the parent */ 818 resmon_part_log_results(self, check, parent); 819 return 0; 820 } 821 if(once) { 822 serf_initiate(self, check); 823 return 0; 824 } 825 if(!check->fire_event) 826 serf_schedule_next(self, NULL, check, NULL); 827 return 0; 828 } 829 666 830 667 831 … … 702 866 }; 703 867 868 noit_module_t resmon_part = { 869 NOIT_MODULE_MAGIC, 870 NOIT_MODULE_ABI_VERSION, 871 "resmon_part", 872 "resmon part resource checker", 873 serf_onload, 874 resmon_config, 875 serf_init, 876 resmon_part_initiate_check 877 }; 878 src/modules/ping_icmp.c
r78402cd r5f413b7 79 79 struct timeval duration; 80 80 81 memset(¤t, 0, sizeof(current)); 82 81 83 data = (struct check_info *)check->closure; 82 84 for(i=0; i<data->expected_count; i++) { … … 107 109 current.state = (avail < 1.0) ? NP_BAD : NP_GOOD; 108 110 current.status = human_buffer; 109 noit_poller_set_state(check, ¤t); 111 noit_poller_set_metric_int(¤t, "count", &data->expected_count); 112 avail *= 100.0; 113 noit_poller_set_metric_float(¤t, "available", &avail); 114 noit_poller_set_metric_float(¤t, "minimum", avail > 0.0 ? &min : NULL); 115 noit_poller_set_metric_float(¤t, "maximum", avail > 0.0 ? &max : NULL); 116 noit_poller_set_metric_float(¤t, "average", avail > 0.0 ? &avg : NULL); 117 noit_poller_set_state(self, check, ¤t); 110 118 } 111 119 static int ping_icmp_timeout(eventer_t e, int mask, … … 452 460 } 453 461 static int ping_icmp_initiate_check(noit_module_t *self, noit_check_t check, 454 int once ) {462 int once, noit_check_t cause) { 455 463 if(!check->closure) check->closure = calloc(1, sizeof(struct check_info)); 456 464 if(once) { src/noit_conf.c
re3c8f10 r5f413b7 14 14 #include "noit_conf.h" 15 15 #include "utils/noit_hash.h" 16 #include "utils/noit_log.h" 16 17 17 18 /* tmp hash impl, replace this with something nice */ … … 89 90 char *value; 90 91 node = xmlXPathNodeSetItem(pobj->nodesetval, i); 91 value = (char *)xmlXPathCastNode SetToString(pobj->nodesetval);92 value = (char *)xmlXPathCastNodeToString(node); 92 93 noit_hash_replace(table, 93 94 strdup((char *)node->name), strlen((char *)node->name), src/noit_module.h
re64e042 r5f413b7 22 22 int (*config)(struct _noit_module *, noit_hash_table *options); 23 23 int (*init)(struct _noit_module *); 24 int (*initiate_check)(struct _noit_module *, noit_check_t check, int once); 24 int (*initiate_check)(struct _noit_module *, noit_check_t check, 25 int once, noit_check_t cause); 25 26 void *opaque_handle; 26 27 } noit_module_t; src/noit_poller.c
re64e042 r5f413b7 26 26 uuid_t foo; 27 27 }; 28 28 29 #define UUID_SIZE sizeof(struct uuid_dummy) 29 30 … … 65 66 char module[256]; 66 67 char name[256]; 68 char oncheck[1024]; 69 int no_period = 0; 70 int no_oncheck = 0; 67 71 int period = 0, timeout = 0; 68 72 uuid_t uuid, out_uuid; … … 88 92 if(!noit_conf_get_stringbuf(sec[i], "../module", module, sizeof(module))) { 89 93 noitL(noit_stderr, "check uuid: '%s' has no module\n", 90 uuid_str);94 uuid_str); 91 95 continue; 92 96 } … … 97 101 if(!noit_conf_get_int(sec[i], "period", &period)) { 98 102 if(!noit_conf_get_int(sec[i], "../period", &period)) { 99 noitL(noit_stderr, "check uuid: '%s' has no period\n", uuid_str); 100 continue; 101 } 103 no_period = 1; 104 } 105 } 106 if(!noit_conf_get_stringbuf(sec[i], "oncheck", oncheck, sizeof(oncheck))) { 107 if(!noit_conf_get_stringbuf(sec[i], "../oncheck", oncheck, sizeof(oncheck))) { 108 oncheck[0] = '\0'; 109 no_oncheck = 1; 110 } 111 } 112 if(no_period && no_oncheck) { 113 noitL(noit_stderr, "check uuid: '%s' has neither period nor oncheck\n", 114 uuid_str); 115 continue; 116 } 117 if(!(no_period || no_oncheck)) { 118 noitL(noit_stderr, "check uuid: '%s' has has on check and period.\n", 119 uuid_str); 120 continue; 102 121 } 103 122 if(!noit_conf_get_int(sec[i], "timeout", &timeout)) { … … 107 126 } 108 127 } 109 if( timeout >= period) {128 if(!no_period && timeout >= period) { 110 129 noitL(noit_stderr, "check uuid: '%s' timeout > period\n", uuid_str); 111 130 timeout = period/2; … … 113 132 options = noit_conf_get_hash(sec[i], "config/*"); 114 133 noit_poller_schedule(target, module, name, options, 115 period, timeout, uuid, out_uuid); 134 period, timeout, oncheck[0] ? oncheck : NULL, 135 uuid, out_uuid); 116 136 noitL(noit_debug, "loaded uuid: %s\n", uuid_str); 117 137 } … … 129 149 mod = noit_module_lookup(check->module); 130 150 if(mod) { 131 mod->initiate_check(mod, check, 0); 151 if((check->flags & NP_DISABLED) == 0) 152 mod->initiate_check(mod, check, 0, NULL); 132 153 } 133 154 else { 134 155 noitL(noit_stderr, "Cannot find module '%s'\n", check->module); 135 } 136 } 137 } 138 156 check->flags |= NP_DISABLED; 157 } 158 } 159 } 160 161 void 162 noit_poller_make_causal_map() { 163 noit_hash_iter iter = NOIT_HASH_ITER_ZERO; 164 uuid_t key_id; 165 int klen; 166 noit_check_t check, parent; 167 while(noit_hash_next(&polls, &iter, (const char **)key_id, &klen, 168 (void **)&check)) { 169 if(check->oncheck) { 170 /* This service is causally triggered by another service */ 171 char fullcheck[1024]; 172 char *name = check->oncheck; 173 char *target = NULL; 174 175 if((target = strchr(check->oncheck, '`')) != NULL) { 176 strlcpy(fullcheck, check->oncheck, target - check->oncheck); 177 name = target + 1; 178 target = fullcheck; 179 } 180 else 181 target = check->target; 182 183 parent = noit_poller_lookup_by_name(target, name); 184 if(!parent) { 185 check->flags |= NP_DISABLED; 186 noitL(noit_stderr, "Disabling check %s/%s, can't find oncheck %s/%s\n", 187 check->target, check->name, target, name); 188 } 189 else { 190 dep_list_t *dep; 191 dep = malloc(sizeof(*dep)); 192 dep->check = check; 193 dep->next = parent->causal_checks; 194 parent->causal_checks = dep; 195 } 196 } 197 } 198 } 139 199 void 140 200 noit_poller_init() { … … 143 203 __check_name_compare); 144 204 noit_poller_load_checks(); 205 noit_poller_make_causal_map(); 145 206 noit_poller_initiate(); 146 207 } … … 153 214 u_int32_t period, 154 215 u_int32_t timeout, 216 const char *oncheck, 155 217 uuid_t in, 156 218 uuid_t out) { … … 182 244 new_check->target = strdup(target); 183 245 new_check->module = strdup(module); 184 new_check->name = name ?strdup(name):NULL;246 new_check->name = name ? strdup(name): NULL; 185 247 186 248 if(config != NULL) { … … 194 256 } 195 257 } 258 new_check->oncheck = oncheck ? strdup(oncheck) : NULL; 196 259 new_check->period = period; 197 260 new_check->timeout = timeout; … … 257 320 tmp_check->target = target; 258 321 tmp_check->name = name; 259 check = noit_skiplist_find(&polls_by_name, &tmp_check, NULL);322 check = noit_skiplist_find(&polls_by_name, tmp_check, NULL); 260 323 free(tmp_check); 261 324 return check; 262 325 } 263 326 264 265 void 266 noit_poller_set_state(noit_check_t check, stats_t *newstate) { 327 static void 328 __free_metric(void *vm) { 329 metric_t *m = vm; 330 free(m->metric_name); 331 if(m->metric_value.i) free(m->metric_value.i); 332 } 333 334 void 335 __stats_add_metric(stats_t *newstate, metric_t *m) { 336 noit_hash_replace(&newstate->metrics, m->metric_name, strlen(m->metric_name), 337 m, NULL, __free_metric); 338 } 339 340 void 341 noit_poller_set_metric_int(stats_t *newstate, char *name, int *value) { 342 metric_t *m = calloc(1, sizeof(*m)); 343 m->metric_name = strdup(name); 344 m->metric_type = METRIC_INT; 345 if(value) { 346 m->metric_value.i = malloc(sizeof(*value)); 347 *(m->metric_value.i) = *value; 348 } 349 __stats_add_metric(newstate, m); 350 } 351 352 void 353 noit_poller_set_metric_float(stats_t *newstate, char *name, float *value) { 354 metric_t *m = calloc(1, sizeof(*m)); 355 m->metric_name = strdup(name); 356 m->metric_type = METRIC_FLOAT; 357 if(value) { 358 m->metric_value.f = malloc(sizeof(*value)); 359 *(m->metric_value.f) = *value; 360 } 361 __stats_add_metric(newstate, m); 362 } 363 364 void 365 noit_poller_set_metric_string(stats_t *newstate, char *name, char *value) { 366 metric_t *m = calloc(1, sizeof(*m)); 367 m->metric_name = strdup(name); 368 m->metric_type = METRIC_STRING; 369 m->metric_value.s = value ? strdup(value) : NULL; 370 __stats_add_metric(newstate, m); 371 } 372 373 void 374 noit_poller_set_state(struct _noit_module *module, 375 noit_check_t check, stats_t *newstate) { 267 376 int report_change = 0; 377 dep_list_t *dep; 268 378 if(check->stats.previous.status) 269 379 free(check->stats.previous.status); 380 noit_hash_destroy(&check->stats.previous.metrics, NULL, __free_metric); 270 381 memcpy(&check->stats.previous, &check->stats.current, sizeof(stats_t)); 271 382 memcpy(&check->stats.current, newstate, sizeof(stats_t)); … … 291 402 __noit_check_state_string(check->stats.current.state)); 292 403 } 293 } 404 for(dep = check->causal_checks; dep; dep = dep->next) { 405 noit_module_t *mod; 406 mod = noit_module_lookup(dep->check->module); 407 assert(mod); 408 noitL(noit_debug, "Firing %s/%s in response to %s/%s\n", 409 dep->check->target, dep->check->name, 410 check->target, check->name); 411 mod->initiate_check(mod, dep->check, 1, check); 412 } 413 } src/noit_poller.h
re64e042 r5f413b7 43 43 44 44 typedef struct { 45 char *metric_name; 46 enum { METRIC_INT, METRIC_FLOAT, METRIC_STRING } metric_type; 47 union { 48 float *f; 49 int *i; 50 char *s; 51 } metric_value; 52 } metric_t; 53 54 typedef struct { 45 55 struct timeval whence; 46 56 int16_t available; … … 48 58 u_int32_t duration; 49 59 char *status; 60 noit_hash_table metrics; 50 61 } stats_t; 51 62 52 typedef struct { 63 typedef struct dep_list { 64 struct noit_check *check; 65 struct dep_list *next; 66 } dep_list_t; 67 68 typedef struct noit_check { 53 69 uuid_t checkid; 54 70 int8_t target_family; … … 61 77 char *name; 62 78 noit_hash_table *config; 79 char *oncheck; /* target`name of the check that fires us */ 63 80 u_int32_t period; /* period of checks in milliseconds */ 64 81 u_int32_t timeout; /* timeout of check in milliseconds */ 65 82 u_int32_t flags; /* NP_KILLED, NP_RUNNING */ 66 83 84 dep_list_t *causal_checks; 67 85 eventer_t fire_event; 68 86 struct timeval last_fire_time; … … 85 103 u_int32_t period, 86 104 u_int32_t timeout, 105 const char *oncheck, 87 106 uuid_t in, 88 107 uuid_t out); … … 97 116 noit_poller_lookup_by_name(char *target, char *name); 98 117 118 struct _noit_module; 99 119 API_EXPORT(void) 100 noit_poller_set_state(noit_check_t check, stats_t *newstate); 120 noit_poller_set_state(struct _noit_module *self, noit_check_t check, 121 stats_t *newstate); 122 123 API_EXPORT(void) 124 noit_poller_set_metric_int(stats_t *newstate, char *name, int *value); 125 API_EXPORT(void) 126 noit_poller_set_metric_float(stats_t *newstate, char *name, float *value); 127 API_EXPORT(void) 128 noit_poller_set_metric_string(stats_t *newstate, char *name, char *value); 101 129 102 130 #endif src/sample.conf
r64e4b06 r5f413b7 17 17 <image>http</image> 18 18 <name>resmon</name> 19 <config>20 <key1>value2</key1>21 <key2>value2</key2>22 < /config>19 </module> 20 <module> 21 <image>http</image> 22 <name>resmon_part</name> 23 23 </module> 24 24 <module> 25 25 <image>http</image> 26 26 <name>http</name> 27 <config>28 <key1>value2</key1>29 <key2>value2</key2>30 </config>31 27 </module> 32 28 </modules> … … 72 68 </check> 73 69 </checkgroup> 70 <checkgroup> 71 <module>resmon_part</module> 72 <timeout>1000</timeout> 73 <oncheck>resmon</oncheck> 74 <check uuid="1b4e28ba-2fa1-11d2-883f-b9a761bde3fe"> 75 <target>10.225.209.36</target> 76 <config> 77 <resmon_module>FAULTS</resmon_module> 78 <resmon_service>hardware</resmon_service> 79 </config> 80 </check> 81 </checkgroup> 74 82 </checks> 75 83 </noit>
