234 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			234 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C
		
	
	
| /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 | |
| #include "config.h"
 | |
| 
 | |
| #include <memcached/protocol_binary.h>
 | |
| 
 | |
| #include <getopt.h>
 | |
| #include <errno.h>
 | |
| #include <stdio.h>
 | |
| #include <string.h>
 | |
| #include <stdlib.h>
 | |
| 
 | |
| /**
 | |
|  * Try to connect to the server
 | |
|  * @param hostname the name of the server
 | |
|  * @param port the port to connect to
 | |
|  * @return a socket descriptor connected to host:port for success, -1 otherwise
 | |
|  */
 | |
| static int connect_server(const char *hostname, const char *port)
 | |
| {
 | |
|     struct addrinfo *ai = NULL;
 | |
|     struct addrinfo hints = { .ai_family = AF_UNSPEC,
 | |
|                               .ai_protocol = IPPROTO_TCP,
 | |
|                               .ai_socktype = SOCK_STREAM };
 | |
| 
 | |
|     if (getaddrinfo(hostname, port, &hints, &ai) != 0) {
 | |
|         return -1;
 | |
|     }
 | |
|     int sock = -1;
 | |
|     if ((sock = socket(ai->ai_family, ai->ai_socktype,
 | |
|                        ai->ai_protocol)) != -1) {
 | |
|         if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
 | |
|             close(sock);
 | |
|             sock = -1;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     freeaddrinfo(ai);
 | |
|     return sock;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Send the chunk of data to the other side, retry if an error occurs
 | |
|  * (or terminate the program if retry wouldn't help us)
 | |
|  * @param sock socket to write data to
 | |
|  * @param buf buffer to send
 | |
|  * @param len length of data to send
 | |
|  */
 | |
| static void retry_send(int sock, const void* buf, size_t len)
 | |
| {
 | |
|     off_t offset = 0;
 | |
|     const char* ptr = buf;
 | |
| 
 | |
|     do {
 | |
|         size_t num_bytes = len - offset;
 | |
|         ssize_t nw = send(sock, ptr + offset, num_bytes, 0);
 | |
|         if (nw == -1) {
 | |
|             if (errno != EINTR) {
 | |
|                 fprintf(stderr, "Failed to write: %s\n", strerror(errno));
 | |
|                 close(sock);
 | |
|                 exit(1);
 | |
|             }
 | |
|         } else {
 | |
|             offset += nw;
 | |
|         }
 | |
|     } while (offset < len);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Receive a fixed number of bytes from the socket.
 | |
|  * (Terminate the program if we encounter a hard error...)
 | |
|  * @param sock socket to receive data from
 | |
|  * @param buf buffer to store data to
 | |
|  * @param len length of data to receive
 | |
|  */
 | |
| static void retry_recv(int sock, void *buf, size_t len) {
 | |
|     if (len == 0) {
 | |
|         return;
 | |
|     }
 | |
|     off_t offset = 0;
 | |
|     do {
 | |
|         ssize_t nr = recv(sock, ((char*)buf) + offset, len - offset, 0);
 | |
|         if (nr == -1) {
 | |
|             if (errno != EINTR) {
 | |
|                 fprintf(stderr, "Failed to read: %s\n", strerror(errno));
 | |
|                 close(sock);
 | |
|                 exit(1);
 | |
|             }
 | |
|         } else {
 | |
|             if (nr == 0) {
 | |
|                 fprintf(stderr, "Connection closed\n");
 | |
|                 close(sock);
 | |
|                 exit(1);
 | |
|             }
 | |
|             offset += nr;
 | |
|         }
 | |
|     } while (offset < len);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Print the key value pair
 | |
|  * @param key key to print
 | |
|  * @param keylen length of key to print
 | |
|  * @param val value to print
 | |
|  * @param vallen length of value
 | |
|  */
 | |
| static void print(const char *key, int keylen, const char *val, int vallen) {
 | |
|     fputs("STAT ", stdout);
 | |
|     (void)fwrite(key, keylen, 1, stdout);
 | |
|     fputs(" ", stdout);
 | |
|     (void)fwrite(val, vallen, 1, stdout);
 | |
|     fputs("\n", stdout);
 | |
|     fflush(stdout);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Request a stat from the server
 | |
|  * @param sock socket connected to the server
 | |
|  * @param key the name of the stat to receive (NULL == ALL)
 | |
|  */
 | |
| static void request_stat(int sock, const char *key)
 | |
| {
 | |
|     uint32_t buffsize = 0;
 | |
|     char *buffer = NULL;
 | |
|     uint16_t keylen = 0;
 | |
|     if (key != NULL) {
 | |
|         keylen = (uint16_t)strlen(key);
 | |
|     }
 | |
| 
 | |
|     protocol_binary_request_stats request = {
 | |
|         .message.header.request = {
 | |
|             .magic = PROTOCOL_BINARY_REQ,
 | |
|             .opcode = PROTOCOL_BINARY_CMD_STAT,
 | |
|             .keylen = htons(keylen),
 | |
|             .bodylen = htonl(keylen)
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     retry_send(sock, &request, sizeof(request));
 | |
|     if (keylen > 0) {
 | |
|         retry_send(sock, key, keylen);
 | |
|     }
 | |
| 
 | |
|     protocol_binary_response_no_extras response;
 | |
|     do {
 | |
|         retry_recv(sock, &response, sizeof(response.bytes));
 | |
|         if (response.message.header.response.keylen != 0) {
 | |
|             uint16_t keylen = ntohs(response.message.header.response.keylen);
 | |
|             uint32_t vallen = ntohl(response.message.header.response.bodylen);
 | |
|             if (vallen > buffsize) {
 | |
|                 if ((buffer = realloc(buffer, vallen)) == NULL) {
 | |
|                     fprintf(stderr, "Failed to allocate memory\n");
 | |
|                     exit(1);
 | |
|                 }
 | |
|                 buffsize = vallen;
 | |
|             }
 | |
|             retry_recv(sock, buffer, vallen);
 | |
|             print(buffer, keylen, buffer + keylen, vallen - keylen);
 | |
|         }
 | |
|     } while (response.message.header.response.keylen != 0);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Program entry point. Connect to a memcached server and use the binary
 | |
|  * protocol to retrieve a given set of stats.
 | |
|  *
 | |
|  * @param argc argument count
 | |
|  * @param argv argument vector
 | |
|  * @return 0 if success, error code otherwise
 | |
|  */
 | |
| int main(int argc, char **argv)
 | |
| {
 | |
|     int cmd;
 | |
|     const char * const default_ports[] = { "memcache", "11211", NULL };
 | |
|     const char *port = NULL;
 | |
|     const char *host = NULL;
 | |
|     char *ptr;
 | |
| 
 | |
|     /* Initialize the socket subsystem */
 | |
|     initialize_sockets();
 | |
| 
 | |
|     while ((cmd = getopt(argc, argv, "h:p:")) != EOF) {
 | |
|         switch (cmd) {
 | |
|         case 'h' :
 | |
|             host = optarg;
 | |
|             ptr = strchr(optarg, ':');
 | |
|             if (ptr != NULL) {
 | |
|                 *ptr = '\0';
 | |
|                 port = ptr + 1;
 | |
|             }
 | |
|             break;
 | |
|         case 'p':
 | |
|             port = optarg;
 | |
|             break;
 | |
|         default:
 | |
|             fprintf(stderr,
 | |
|                     "Usage mcstat [-h host[:port]] [-p port] [statkey]*\n");
 | |
|             return 1;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (host == NULL) {
 | |
|         host = "localhost";
 | |
|     }
 | |
| 
 | |
|     int sock = -1;
 | |
|     if (port == NULL) {
 | |
|         int ii = 0;
 | |
|         do {
 | |
|             port = default_ports[ii++];
 | |
|             sock = connect_server(host, port);
 | |
|         } while (sock == -1 && default_ports[ii] != NULL);
 | |
|     } else {
 | |
|         sock = connect_server(host, port);
 | |
|     }
 | |
| 
 | |
|     if (sock == -1) {
 | |
|         fprintf(stderr, "Failed to connect to memcached server (%s:%s): %s\n",
 | |
|                 host, port, strerror(errno));
 | |
|         return 1;
 | |
|     }
 | |
| 
 | |
|     if (optind == argc) {
 | |
|         request_stat(sock, NULL);
 | |
|     } else {
 | |
|         for (int ii = optind; ii < argc; ++ii) {
 | |
|             request_stat(sock, argv[ii]);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     close(sock);
 | |
| 
 | |
|     return 0;
 | |
| }
 |