Пример #1
0
void *
fqd_worker_thread(void *arg)
{
  struct incoming_message *m;
  int tindex = *(int*) arg;

  ck_fifo_spsc_t *q = &work_queues[tindex];
  uint32_t *backlog = &work_queue_backlogs[tindex];

  while (!worker_thread_shutdown) {
    if (!CK_FIFO_SPSC_ISEMPTY(q)) {
      if (ck_fifo_spsc_dequeue(q, &m)) {
        if (m != NULL) {
          ck_pr_dec_32(backlog);
          fq_msg *copy = fq_msg_alloc_BLANK(m->msg->payload_len);
          if (copy == NULL) {
            continue;
          }
          memcpy(copy, m->msg, sizeof(fq_msg) + m->msg->payload_len);
          /* reset the refcnt on the copy since the memcpy above will have overwritten it */
          copy->refcnt = 1;
          /* the copy will be freed as normal so eliminate cleanup_stack pointer */
          copy->cleanup_stack = NULL;

          /* we are done with the incoming message, deref */
          fq_msg_deref(m->msg);

          fqd_inject_message(m->client, copy);
	  fqd_remote_client_deref((remote_client *)m->client);
          free(m);
        }
      }
    } else {
      usleep(1000);
    }
  }

  /* drain the queue before exiting */
  if (!CK_FIFO_SPSC_ISEMPTY(q)) {
    ck_fifo_spsc_dequeue_lock(q);
    if (ck_fifo_spsc_dequeue(q, &m)) {
      if (m != NULL) {
        ck_pr_dec_32(backlog);
        fq_msg *copy = fq_msg_alloc_BLANK(m->msg->payload_len);
        memcpy(copy, m->msg, sizeof(fq_msg) + m->msg->payload_len);
        /* the copy will be freed as normal so eliminate cleanup_stack pointer */
        copy->cleanup_stack = NULL;
        /* we are done with the incoming message, drop it on it's cleanup stack */
        fqd_inject_message(m->client, copy);
        fq_msg_deref(m->msg);
        free(m);
      }
    }
    ck_fifo_spsc_dequeue_unlock(q);
  }
  usleep(10);
  return NULL;
}
Пример #2
0
int main(int argc, char **argv) {
  hrtime_t s, f;
  uint64_t cnt = 0, icnt = 0, icnt_total = 0;
  int rcvd = 0;
  fq_client c;
  fq_bind_req breq;
  fq_msg *m;
  signal(SIGPIPE, SIG_IGN);
  fq_client_init(&c, 0, logger);
  if(argc < 5) {
    fprintf(stderr, "%s <host> <port> <user> <pass> [size [count]]\n",
            argv[0]);
    exit(-1);
  }
  fq_client_creds(c, argv[1], atoi(argv[2]), argv[3], argv[4]);
  fq_client_heartbeat(c, 1000);
  fq_client_set_backlog(c, 10000, 100);
  fq_client_connect(c);

  memset(&breq, 0, sizeof(breq));
  memcpy(breq.exchange.name, "maryland", 8);
  breq.exchange.len = 8;
  breq.peermode = 0;
  breq.program = (char *)"prefix:\"test.prefix.\"";

  fq_client_bind(c, &breq);
  while(breq.out__route_id == 0) usleep(100);
  printf("route set -> %u\n", breq.out__route_id);
  if(breq.out__route_id == FQ_BIND_ILLEGAL) {
    fprintf(stderr, "Failure to bind...\n");
    exit(-1);
  }

  s = fq_gethrtime();
  while(1) {
    f = fq_gethrtime();
    while(m = fq_client_receive(c)) {
      icnt++;
      icnt_total++;
      rcvd++;
      fq_msg_deref(m);
    }
    usleep(1000);
    if(f-s > 1000000000) {
      print_rate(c, s, f, cnt, icnt);
      printf("total: %llu\n", (unsigned long long)icnt_total);
      icnt = 0;
      cnt = 0;
      s = f;
    }
  }
  (void) argc;
  return 0;
}
Пример #3
0
static int
noit_fq_submit(iep_thread_driver_t *dr,
               const char *payload, size_t payloadlen) {
  int i;
  struct fq_driver *driver = (struct fq_driver *)dr;
  const char *routingkey = driver->routingkey;
  fq_msg *msg;

  if(*payload == 'M' ||
     *payload == 'S' ||
     *payload == 'C' ||
     (*payload == 'B' && (payload[1] == '1' || payload[1] == '2'))) {
    char uuid_str[32 * 2 + 1];
    int account_id, check_id;
    if(extract_uuid_from_jlog(payload, payloadlen,
                              &account_id, &check_id, uuid_str)) {
      if(*routingkey) {
        char *replace;
        int newlen = strlen(driver->routingkey) + 1 + sizeof(uuid_str) + 2 * 32;
        replace = alloca(newlen);
        snprintf(replace, newlen, "%s.%x.%x.%d.%d%s", driver->routingkey,
                 account_id%16, (account_id/16)%16, account_id,
                 check_id, uuid_str);
        routingkey = replace;
      }
    }
  }

  /* Setup our message */
  msg = fq_msg_alloc(payload, payloadlen);
  if(msg == NULL) {
    driver->allocation_failures++;
    return -1;
  }
  driver->msg_cnt++;
  fq_msg_exchange(msg, driver->exchange, strlen(driver->exchange));
  noitL(noit_debug, "route[%s] -> %s\n", driver->exchange, routingkey);
  fq_msg_route(msg, routingkey, strlen(routingkey));
  fq_msg_id(msg, NULL);

  for(i=0; i<driver->nhosts; i++) {
    if(fq_client_publish(driver->client[i], msg) == 1)
      BUMPSTAT(i, publications);
    else
      BUMPSTAT(i, client_tx_drop);
  }
  fq_msg_deref(msg);
  return 0;
}
Пример #4
0
int main(int argc, char **argv) {
  hrtime_t s, f;
  uint64_t cnt = 0, icnt = 0, icnt_total = 0;
  int rcvd = 0;
  fq_client c;
  fq_msg *m;

  char *fq_debug = getenv("FQ_DEBUG");
  if(fq_debug) fq_debug_set_bits(atoi(fq_debug));
  signal(SIGPIPE, SIG_IGN);
  fq_client_init(&c, 0, logger);
  if(fq_client_hooks(c, &hooks)) {
    fprintf(stderr, "Can't register hooks\n");
    exit(-1);
  }
  if(argc < 5) {
    fprintf(stderr, "%s <host> <port> <user> <pass> [size [count]]\n",
            argv[0]);
    exit(-1);
  }
  fq_client_hooks(c, &hooks);
  fq_client_creds(c, argv[1], atoi(argv[2]), argv[3], argv[4]);
  fq_client_heartbeat(c, 1000);
  fq_client_set_backlog(c, 10000, 100);
  fq_client_connect(c);

  s = fq_gethrtime();
  while(1) {
    f = fq_gethrtime();
    while(m = fq_client_receive(c)) {
      icnt++;
      icnt_total++;
      rcvd++;
      fq_msg_deref(m);
    }
    usleep(1000);
    if(f-s > 1000000000) {
      print_rate(c, s, f, cnt, icnt);
      printf("total: %llu\n", (unsigned long long)icnt_total);
      icnt = 0;
      cnt = 0;
      s = f;
    }
  }
  (void) argc;
  return 0;
}
Пример #5
0
static void
fq_client_read_complete(void *closure, fq_msg *msg) {
  fq_conn_s *conn_s = (fq_conn_s *)closure;

  if(conn_s->message_hook && conn_s->message_hook(conn_s, msg)) {
    fq_msg_deref(msg);
  }
  else {

    ck_fifo_spsc_enqueue_lock(&conn_s->backq);
    ck_fifo_spsc_entry_t *fifo_entry = ck_fifo_spsc_recycle(&conn_s->backq);
    if (unlikely(fifo_entry == NULL)) {
      fifo_entry = malloc(sizeof(ck_fifo_spsc_entry_t));
    }
    ck_fifo_spsc_enqueue(&conn_s->backq, fifo_entry, msg);
    ck_fifo_spsc_enqueue_unlock(&conn_s->backq);
  }
}
Пример #6
0
static void
http_req_clean(struct http_req *req) {
  ck_ht_entry_t *cursor;
  ck_ht_iterator_t iterator = CK_HT_ITERATOR_INITIALIZER;

  while(ck_ht_next(&req->headers, &iterator, &cursor)) {
    ck_ht_hash_t hv;
    char *key = ck_ht_entry_key(cursor);
    char *value = ck_ht_entry_value(cursor);
    ck_ht_hash(&hv, &req->headers, key, strlen(key));
    ck_ht_remove_spmc(&req->headers, hv, cursor);
    free(key);
    free(value);
  }

  ck_ht_iterator_init(&iterator);

  while(ck_ht_next(&req->query_params, &iterator, &cursor)) {
    ck_ht_hash_t hv;
    char *key = ck_ht_entry_key(cursor);
    char *value = ck_ht_entry_value(cursor);
    ck_ht_hash(&hv, &req->headers, key, strlen(key));
    ck_ht_remove_spmc(&req->headers, hv, cursor);
    free(key);
    free(value);
  }

  if(req->url) free(req->url);
  /* req->qs isn't allocated */
  if(req->status) free(req->status);
  if(req->fldname) free(req->fldname);
  if(req->error) free(req->error);
  if(req->msg) fq_msg_deref(req->msg);

  req->url = NULL;
  req->qs = NULL;
  req->status = NULL;
  req->fldname = NULL;
  req->error = NULL;
  req->body_len = 0;
  req->body_read = 0;
  req->msg = NULL;
  req->expect_continue = HTTP_EXPECT_NONE;
}
Пример #7
0
static void
fqd_data_driver(remote_client *parent) {
  remote_data_client *me = parent->data;
  fq_msg *inflight = NULL;
  size_t inflight_sofar = 0;
  buffered_msg_reader *ctx = NULL;
  int had_msgs = 1, flags, needs_write = 1;

  if(((flags = fcntl(me->fd, F_GETFL, 0)) == -1) ||
     (fcntl(me->fd, F_SETFL, flags | O_NONBLOCK) == -1))
    return;

  ctx = fq_buffered_msg_reader_alloc(me->fd, (me->mode == FQ_PROTO_PEER_MODE));
  while(1) {
    uint32_t msgs_in = me->msgs_in, msgs_out = me->msgs_out;
    int rv, timeout_ms = 1000;
    struct pollfd pfd;
    pfd.fd = me->fd;
    pfd.events = POLLIN|POLLHUP;
    if(needs_write) pfd.events |= POLLOUT;
    pfd.revents = 0;
    if(parent->heartbeat_ms && parent->heartbeat_ms < timeout_ms)
      timeout_ms = parent->heartbeat_ms;
    /* if we had msgs, but aren't waiting for write,
     * then we set a very short timeout
     */
    if(had_msgs && !needs_write) timeout_ms = 1;

    rv = poll(&pfd, 1, timeout_ms);
    if(rv < 0) break;

    had_msgs = 0;
    if(rv > 0 && (pfd.revents & POLLIN)) {
      me->last_heartbeat = me->last_activity = fq_gethrtime();
      if(fq_buffered_msg_read(ctx, fqd_dss_read_complete, parent) < 0) {
        fq_debug(FQ_DEBUG_IO, "client read error\n");
        break;
      }
      had_msgs = 1;
    }

    if(!needs_write || (rv > 0 && (pfd.revents & POLLOUT))) {
      fq_msg *m;
      needs_write = 0;
     haveanother:
      m = inflight ? inflight
                   : parent->queue ? fqd_queue_dequeue(parent->queue)
                                   : NULL;

      /* If we're in peer mode, we can just toss messages the remote has seen */
      if(m && me->mode == FQ_PROTO_PEER_MODE &&
         fq_find_in_hops(me->peer_id, m) >= 0) {
        fq_msg_deref(m);
        goto haveanother;
      }

      inflight = NULL;
      while(m) {
        int written;
        size_t octets_out = 0;
        had_msgs = 1;
        written = fq_client_write_msg(me->fd, 1, m, inflight_sofar, &octets_out);
        if(written > 0) inflight_sofar += written;
        if(octets_out) me->octets_out += octets_out;

        if(written > 0 || (written < 0 && errno == EAGAIN)) {
          inflight = m;
          needs_write = 1;
          break;
        }
        else if(written < 0) {
          fq_debug(FQ_DEBUG_IO, "client write error\n");
          goto broken;
        }

        if(FQ_MESSAGE_DELIVER_ENABLED()) {
          fq_dtrace_msg_t dmsg;
          fq_dtrace_remote_client_t dpc;
          fq_dtrace_remote_data_client_t dme;
          DTRACE_PACK_MSG(&dmsg, m);
          DTRACE_PACK_CLIENT(&dpc, parent);
          DTRACE_PACK_DATA_CLIENT(&dme, me);
          FQ_MESSAGE_DELIVER(&dpc, &dme, &dmsg);
        }
        fq_msg_deref(m);
        me->msgs_out++;
        inflight_sofar = 0;
        m = parent->queue ? fqd_queue_dequeue(parent->queue) : NULL;
      }
    }

    if(me->msgs_in != msgs_in || me->msgs_out != msgs_out)
      fq_debug(FQ_DEBUG_MSG, "Round.... %d in, %d out\n", me->msgs_in, me->msgs_out);
  }
broken:
  if(inflight) {
    /* We're screwed here... we might have delivered it. so just toss it */
    fq_msg_deref(inflight);
  }
  if(ctx) fq_buffered_msg_reader_free(ctx);
  parent->data = NULL;
  fq_debug(FQ_DEBUG_IO, "data path from client ended: %s\n", parent->pretty);
}
Пример #8
0
static void
fq_data_worker_loop(fq_conn_s *conn_s) {
  buffered_msg_reader *ctx = NULL;
  ctx = fq_buffered_msg_reader_alloc(conn_s->data_fd, 1);
  while(conn_s->cmd_fd >= 0 && conn_s->data_fd >= 0 && conn_s->stop == 0) {
    int rv;
    int wait_ms = 500, needs_write = 0, mask, write_rv;
    if(conn_s->tosend) goto the_thick_of_it;
    ck_fifo_spsc_dequeue_lock(&conn_s->q);
    while(ck_fifo_spsc_dequeue(&conn_s->q, &conn_s->tosend) == true) {
      conn_s->tosend_offset = 0;
      ck_pr_dec_uint(&conn_s->qlen);
     the_thick_of_it:
#ifdef DEBUG
      fq_debug(FQ_DEBUG_MSG, "dequeue message to submit to server\n");
#endif
      write_rv = fq_client_write_msg(conn_s->data_fd, conn_s->peermode,
                                     conn_s->tosend, conn_s->tosend_offset, NULL);
      if(write_rv > 0) {
        conn_s->tosend_offset += write_rv;
        wait_ms = 0;
        break;
      }
      if(write_rv < 0) {
        if(errno == EAGAIN) {
          needs_write = 1;
          break;
        }
        if(conn_s->errorlog) {
          char errbuf[128];
          snprintf(errbuf, sizeof(errbuf), "data write error: %s\n", strerror(errno));
          conn_s->errorlog(conn_s, errbuf);
        }
        ck_fifo_spsc_dequeue_unlock(&conn_s->q);
        goto finish;
      }
      fq_msg_deref(conn_s->tosend);
      conn_s->tosend = NULL;
      conn_s->tosend_offset = 0;
      wait_ms = 0;
    }
    ck_fifo_spsc_dequeue_unlock(&conn_s->q);

    rv = fq_client_wfrw_internal(conn_s->data_fd, 1, needs_write, wait_ms, &mask);
    fq_debug(FQ_DEBUG_CONN, "fq_client_wfrw_internal(data:%d) -> %d\n", conn_s->data_fd, rv);
    if(rv < 0) {
      if(conn_s->errorlog) {
        char errbuf[128];
        snprintf(errbuf, sizeof(errbuf), "data read error: %s\n", strerror(errno));
        conn_s->errorlog(conn_s, errbuf);
      }
      goto finish;
    }
    if(rv > 0 && (mask & POLLIN)) {
      if(fq_buffered_msg_read(ctx, fq_client_read_complete, conn_s) < 0) {
        if(conn_s->errorlog) conn_s->errorlog(conn_s, "data read: end-of-line\n");
        goto finish;
      }
    }
  }
finish:
  fq_clear_message_cleanup_stack();
  if(ctx) fq_buffered_msg_reader_free(ctx);
#ifdef DEBUG
  fq_debug(FQ_DEBUG_CONN, "cmd_fd -> %d, stop -> %d\n", conn_s->cmd_fd, conn_s->stop);
#endif
}
Пример #9
0
Файл: fqc.c Проект: denji/fq
int main(int argc, char **argv) {
  hrtime_t s0, s, f, f0;
  uint64_t cnt = 0, icnt = 0;
  int psize = 0, i = 0, rcvd = 0;
  fq_client c;
  fq_bind_req breq;
  fq_msg *m;
  signal(SIGPIPE, SIG_IGN);
  fq_client_init(&c, 0, logger);
  if(argc < 5) {
    fprintf(stderr, "%s <host> <port> <user> <pass> [size [count]]\n",
            argv[0]);
    exit(-1);
  }
  fq_client_creds(c, argv[1], atoi(argv[2]), argv[3], argv[4]);
  fq_client_heartbeat(c, 1000);
  fq_client_set_backlog(c, 10000, 100);
  fq_client_connect(c);

  memset(&breq, 0, sizeof(breq));
  memcpy(breq.exchange.name, "maryland", 8);
  breq.exchange.len = 8;
  breq.peermode = 0;
  breq.program = (char *)"prefix:\"test.prefix.\"";

  fq_client_bind(c, &breq);
  while(breq.out__route_id == 0) usleep(100);
  printf("route set -> %u\n", breq.out__route_id);
  if(breq.out__route_id == FQ_BIND_ILLEGAL) {
    fprintf(stderr, "Failure to bind...\n");
    exit(-1);
  }

  if(argc > 5) {
     psize = atoi(argv[5]);
  }
  printf("payload size -> %d\n", psize);
  if(argc > 6) {
    send_count = atoi(argv[6]);
  }
  printf("message count -> %d\n", send_count);

  s0 = s = fq_gethrtime();
  while(i < send_count || fq_client_data_backlog(c) > 0) {
    if(i < send_count) {
      m = fq_msg_alloc_BLANK(psize);
      memset(m->payload, 0, psize);
      fq_msg_exchange(m, "maryland", 8);
      fq_msg_route(m, "test.prefix.foo", 15);
      fq_msg_id(m, NULL);
      fq_client_publish(c, m);
      cnt++;
      i++;
      fq_msg_free(m);
    }
    else usleep(100);


    f = fq_gethrtime();
    while(m = fq_client_receive(c)) {
      icnt++;
      rcvd++;
      fq_msg_deref(m);
    }
    if(f-s > 1000000000) {
      print_rate(c, s, f, cnt, icnt);
      icnt = 0;
      cnt = 0;
      s = f;
    }
  }
  f0 = fq_gethrtime();
  print_rate(c, s0, f0, i, 0);
  do {
    icnt=0;
    while(m = fq_client_receive(c)) {
      icnt++;
      rcvd++;
      fq_msg_deref(m);
    }
  } while(rcvd < send_count);
  f0 = fq_gethrtime();
  print_rate(c, s0, f0, 0, rcvd);
  printf("Total received during test: %d\n", rcvd);

  (void) argc;
  return 0;
}
Пример #10
0
void fq_buffered_msg_reader_free(buffered_msg_reader *f) {
  assert(f->msg->refcnt == 1);
  fq_msg_deref(f->msg);
  if(f->copy) fq_msg_deref(f->copy);
  free(f);
}
Пример #11
0
static int
noit_fq_submit(iep_thread_driver_t *dr,
               const char *payload, size_t payloadlen) {
  int i;
  struct fq_driver *driver = (struct fq_driver *)dr;
  const char *routingkey = driver->routingkey;
  mtev_hash_table *filtered_metrics;
  char uuid_formatted_str[UUID_STR_LEN+1];
  bool is_bundle = false, is_metric = false, send = false;
  fq_msg *msg;
  char *metric = NULL;

  if(*payload == 'M' ||
     *payload == 'S' ||
     *payload == 'C' ||
     (*payload == 'H' && payload[1] == '1') ||
     (*payload == 'F' && payload[1] == '1') ||
     (*payload == 'B' && (payload[1] == '1' || payload[1] == '2'))) {
    char uuid_str[32 * 2 + 1];
    int account_id, check_id;

    if (*payload == 'B') is_bundle = true;
    else if ((*payload == 'H') || (*payload == 'M')) {
      is_metric = true;
    }

    if(extract_uuid_from_jlog(payload, payloadlen,
                              &account_id, &check_id, uuid_str,
                              uuid_formatted_str, &metric)) {
      if(*routingkey) {
        char *replace;
        int newlen = strlen(driver->routingkey) + 1 + sizeof(uuid_str) + 2 * 32;
        replace = alloca(newlen);
        snprintf(replace, newlen, "%s.%x.%x.%d.%d%s", driver->routingkey,
                 account_id%16, (account_id/16)%16, account_id,
                 check_id, uuid_str);
        routingkey = replace;
      }
    }
  }

  /* Let through any messages that aren't metrics or bundles */
  if (!is_bundle && !is_metric) {
    send = true;
  }

  /* Setup our message */
  msg = fq_msg_alloc(payload, payloadlen);
  if(msg == NULL) {
    driver->allocation_failures++;
    if (metric) free(metric);
    return -1;
  }
  driver->msg_cnt++;
  fq_msg_exchange(msg, driver->exchange, strlen(driver->exchange));
  mtevL(mtev_debug, "route[%s] -> %s\n", driver->exchange, routingkey);
  fq_msg_route(msg, routingkey, strlen(routingkey));
  fq_msg_id(msg, NULL);

  if (global_fq_ctx.round_robin) {
    int checked = 0, good = 0;
    time_t cur_time;
    while (1) {
      if (!global_fq_ctx.down_host[global_fq_ctx.round_robin_target]) {
        good = 1;
        break;
      }
      cur_time = time(NULL);
      if (cur_time - global_fq_ctx.last_error[global_fq_ctx.round_robin_target] >= 10) {
        global_fq_ctx.down_host[global_fq_ctx.round_robin_target] = false;
        good = 1;
        break;
      } 
      global_fq_ctx.round_robin_target = (global_fq_ctx.round_robin_target+1) % driver->nhosts;
      checked++;
      if (checked == driver->nhosts) {
        /* This means everybody is down.... just try to send to whatever fq
           we're pointing at */
        break;
      }
    }
    if (good) {
      if(fq_client_publish(driver->client[global_fq_ctx.round_robin_target], msg) == 1) {
        BUMPSTAT(global_fq_ctx.round_robin_target, publications);
      }
      else {
        BUMPSTAT(global_fq_ctx.round_robin_target, client_tx_drop);
      }
    }
    /* Go ahead and try to publish to the hosts that are down, just in
       case they've come back up. This should help minimize lost messages */
    for (i=0; i<driver->nhosts; i++) {
      if (global_fq_ctx.down_host[i]) {
        if(fq_client_publish(driver->client[i], msg) == 1) {
          BUMPSTAT(i, publications);
        }
        else {
          BUMPSTAT(i, client_tx_drop);
        }
      }
    }
    global_fq_ctx.round_robin_target = (global_fq_ctx.round_robin_target+1) % driver->nhosts;
  }
  else {
    for(i=0; i<driver->nhosts; i++) {
      if(fq_client_publish(driver->client[i], msg) == 1) {
        BUMPSTAT(i, publications);
      }
      else {
        BUMPSTAT(i, client_tx_drop);
      }
    }
  }

  fq_msg_deref(msg);
  if (!send) {
    if(mtev_hash_retrieve(&filtered_checks_hash, uuid_formatted_str, strlen(uuid_formatted_str), (void**)&filtered_metrics)) {
      if (is_bundle || (is_metric && mtev_hash_size(filtered_metrics) == 0)) {
        send = true;
      }
      else if (is_metric) {
        void *tmp;
        if (mtev_hash_retrieve(filtered_metrics, metric, strlen(metric), &tmp)) {
          send = true;
        }
      }
    }
  }

  if (global_fq_ctx.filtered_exchange[0] && send) {
    fq_msg *msg2;
    msg2 = fq_msg_alloc(payload, payloadlen);
    fq_msg_exchange(msg2, driver->filtered_exchange, strlen(driver->filtered_exchange));
    mtevL(mtev_debug, "route[%s] -> %s\n", driver->filtered_exchange, routingkey);
    fq_msg_route(msg2, routingkey, strlen(routingkey));
    fq_msg_id(msg2, NULL);
    for(i=0; i<driver->nhosts; i++) {
      if(fq_client_publish(driver->client[i], msg2) == 1) {
        BUMPSTAT(i, publications);
      }
      else {
        BUMPSTAT(i, client_tx_drop);
      }
    }
    fq_msg_deref(msg2);
  }
  if (metric) free(metric);
  return 0;
}