2 * collectd - src/memcached.c
3 * Copyright (C) 2007 Antony Dovgal, heavily based on hddtemp.c
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 * Antony Dovgal <tony at daylessday dot org>
27 #include "configfile.h"
31 # include <sys/socket.h>
32 # include <netinet/in.h>
33 # include <netinet/tcp.h>
34 # include <libgen.h> /* for basename */
36 #if HAVE_LINUX_MAJOR_H
37 # include <linux/major.h>
40 #define MEMCACHED_DEF_HOST "127.0.0.1"
41 #define MEMCACHED_DEF_PORT "11211"
43 #define MEMCACHED_RETRY_COUNT 100
45 static const char *config_keys[] =
51 static int config_keys_num = 2;
53 static char *memcached_host = NULL;
54 static char memcached_port[16];
56 static int memcached_query_daemon (char *buffer, int buffer_size) /* {{{ */
65 struct addrinfo ai_hints;
66 struct addrinfo *ai_list, *ai_ptr;
69 memset (&ai_hints, '\0', sizeof (ai_hints));
70 ai_hints.ai_flags = 0;
72 /* ai_hints.ai_flags |= AI_ADDRCONFIG; */
74 ai_hints.ai_family = AF_INET;
75 ai_hints.ai_socktype = SOCK_STREAM;
76 ai_hints.ai_protocol = 0;
78 host = memcached_host;
80 host = MEMCACHED_DEF_HOST;
83 port = memcached_port;
84 if (strlen (port) == 0) {
85 port = MEMCACHED_DEF_PORT;
88 if ((ai_return = getaddrinfo (host, port, NULL, &ai_list)) != 0) {
90 ERROR ("memcached: getaddrinfo (%s, %s): %s",
92 (ai_return == EAI_SYSTEM)
93 ? sstrerror (errno, errbuf, sizeof (errbuf))
94 : gai_strerror (ai_return));
99 for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
100 /* create our socket descriptor */
101 if ((fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol)) < 0) {
103 ERROR ("memcached: socket: %s", sstrerror (errno, errbuf, sizeof (errbuf)));
107 /* connect to the memcached daemon */
108 if (connect (fd, (struct sockaddr *) ai_ptr->ai_addr, ai_ptr->ai_addrlen)) {
110 shutdown(fd, SHUT_RDWR);
116 /* A socket could be opened and connecting succeeded. We're
121 freeaddrinfo (ai_list);
124 ERROR ("memcached: Could not connect to daemon.");
128 if (send(fd, "stats\r\n", sizeof("stats\r\n") - 1, MSG_DONTWAIT) != (sizeof("stats\r\n") - 1)) {
129 ERROR ("memcached: Could not send command to the memcached daemon.");
138 p.events = POLLIN|POLLERR|POLLHUP;
144 ERROR ("memcached: poll() failed or timed out");
149 /* receive data from the memcached daemon */
150 memset (buffer, '\0', buffer_size);
153 while ((status = recv (fd, buffer + buffer_fill, buffer_size - buffer_fill, MSG_DONTWAIT)) != 0) {
154 if (i > MEMCACHED_RETRY_COUNT) {
155 ERROR("recv() timed out");
163 if (errno == EAGAIN) {
167 ERROR ("memcached: Error reading from socket: %s",
168 sstrerror (errno, errbuf, sizeof (errbuf)));
169 shutdown(fd, SHUT_RDWR);
173 buffer_fill += status;
175 if (buffer_fill > 3 && buffer[buffer_fill-5] == 'E' && buffer[buffer_fill-4] == 'N' && buffer[buffer_fill-3] == 'D') {
176 /* we got all the data */
181 if (buffer_fill >= buffer_size) {
182 buffer[buffer_size - 1] = '\0';
183 WARNING ("memcached: Message from memcached has been truncated.");
184 } else if (buffer_fill == 0) {
185 WARNING ("memcached: Peer has unexpectedly shut down the socket. "
186 "Buffer: `%s'", buffer);
187 shutdown(fd, SHUT_RDWR);
192 shutdown(fd, SHUT_RDWR);
198 static int memcached_config (const char *key, const char *value) /* {{{ */
200 if (strcasecmp (key, "Host") == 0) {
201 if (memcached_host != NULL) {
202 free (memcached_host);
204 memcached_host = strdup (value);
205 } else if (strcasecmp (key, "Port") == 0) {
206 int port = (int) (atof (value));
207 if ((port > 0) && (port <= 65535)) {
208 snprintf (memcached_port, sizeof (memcached_port), "%i", port);
210 strncpy (memcached_port, value, sizeof (memcached_port));
212 memcached_port[sizeof (memcached_port) - 1] = '\0';
222 static void memcached_submit_items(double curr_items, unsigned long long total_items) /* {{{ */
225 value_list_t vl = VALUE_LIST_INIT;
227 values[0].gauge = curr_items;
228 values[1].counter = total_items;
232 vl.time = time (NULL);
233 strcpy (vl.host, hostname_g);
234 strcpy (vl.plugin, "memcached");
236 plugin_dispatch_values ("memcached_items", &vl);
241 static void memcached_submit_connections(double curr_connections, unsigned long long total_connections) /* {{{ */
244 value_list_t vl = VALUE_LIST_INIT;
246 values[0].gauge = curr_connections;
247 values[1].counter = total_connections;
251 vl.time = time (NULL);
252 strcpy (vl.host, hostname_g);
253 strcpy (vl.plugin, "memcached");
255 plugin_dispatch_values ("memcached_connections", &vl);
259 static void memcached_submit_bytes(unsigned long long bytes_read, unsigned long long bytes_written) /* {{{ */
262 value_list_t vl = VALUE_LIST_INIT;
264 values[0].counter = bytes_read;
265 values[1].counter = bytes_written;
269 vl.time = time (NULL);
270 strcpy (vl.host, hostname_g);
271 strcpy (vl.plugin, "memcached");
273 plugin_dispatch_values ("memcached_bytes", &vl);
277 static void memcached_submit_cmd(unsigned long long cmd_get, unsigned long long cmd_set, unsigned long long get_hits, unsigned long long get_misses) /* {{{ */
280 value_list_t vl = VALUE_LIST_INIT;
282 values[0].counter = cmd_get;
283 values[1].counter = cmd_set;
284 values[2].counter = get_hits;
285 values[3].counter = get_misses;
289 vl.time = time (NULL);
290 strcpy (vl.host, hostname_g);
291 strcpy (vl.plugin, "memcached");
293 plugin_dispatch_values ("memcached_cmd", &vl);
297 static void memcached_submit_rusage(unsigned long long rusage_user, unsigned long long rusage_system) /* {{{ */
300 value_list_t vl = VALUE_LIST_INIT;
302 values[0].counter = rusage_user;
303 values[1].counter = rusage_system;
307 vl.time = time (NULL);
308 strcpy (vl.host, hostname_g);
309 strcpy (vl.plugin, "memcached");
311 plugin_dispatch_values ("memcached_rusage", &vl);
315 static int memcached_read (void) /* {{{ */
325 unsigned long long total_connections = 0, bytes_read = 0, bytes_written = 0, cmd_get = 0, cmd_set = 0, get_hits = 0, get_misses = 0, rusage_user = 0, rusage_system = 0;
326 double curr_connections = 0;
328 /* get data from daemon */
329 if (memcached_query_daemon (buf, sizeof (buf)) < 0) {
335 while ((lines[lines_num] = strtok_r (ptr, "\n\r", &saveptr)) != NULL) {
339 if (lines_num >= 127) break;
342 #define FIELD_IS(cnst) \
343 (sizeof(cnst) - 1) == name_len && memcmp(cnst, fields[1], sizeof(cnst)) == 0
345 for (i = 0; i < lines_num; i++) {
348 fields_num = strsplit(lines[i], fields, 3);
349 if (fields_num != 3) continue;
351 name_len = strlen(fields[1]);
352 if (name_len == 0) continue;
354 if (FIELD_IS("rusage_user")) {
355 rusage_user = atoll(fields[2]);
356 } else if (FIELD_IS("rusage_system")) {
357 rusage_system = atoll(fields[2]);
358 /* } else if (FIELD_IS("curr_items")) {
359 curr_items = atof(fields[2]);
360 } else if (FIELD_IS("total_items")) {
361 total_items = atoll(fields[2]);
362 } else if (FIELD_IS("bytes")) {
363 bytes = atof(fields[2]); */
364 } else if (FIELD_IS("curr_connections")) {
365 curr_connections = atof(fields[2]);
366 } else if (FIELD_IS("total_connections")) {
367 total_connections = atoll(fields[2]);
368 /* } else if (FIELD_IS("connection_structures")) {
369 connection_structures = atof(fields[2]); */
370 } else if (FIELD_IS("cmd_get")) {
371 cmd_get = atoll(fields[2]);
372 } else if (FIELD_IS("cmd_set")) {
373 cmd_set = atoll(fields[2]);
374 } else if (FIELD_IS("get_hits")) {
375 get_hits = atoll(fields[2]);
376 } else if (FIELD_IS("get_misses")) {
377 get_misses = atoll(fields[2]);
378 /* } else if (FIELD_IS("evictions")) {
379 evictions = atoll(fields[2]); */
380 } else if (FIELD_IS("bytes_read")) {
381 bytes_read = atoll(fields[2]);
382 } else if (FIELD_IS("bytes_written")) {
383 bytes_written = atoll(fields[2]);
384 /* } else if (FIELD_IS("limit_maxbytes")) {
385 limit_maxbytes = atof(fields[2]);
386 } else if (FIELD_IS("threads")) {
387 threads = atof(fields[2]); */
392 memcached_submit_items(curr_items, total_items);
394 memcached_submit_connections(curr_connections, total_connections);
395 memcached_submit_bytes(bytes_read, bytes_written);
396 memcached_submit_cmd(cmd_get, cmd_set, get_hits, get_misses);
397 memcached_submit_rusage(rusage_user, rusage_system);
403 void module_register (void) /* {{{ */
405 plugin_register_config ("memcached", memcached_config, config_keys, config_keys_num);
406 plugin_register_read ("memcached", memcached_read);
415 * vim600: sw=4 ts=4 fdm=marker