Skip to content

Commit

Permalink
feat: containers memory check plugin
Browse files Browse the repository at this point in the history
Signed-off-by: Davide Madrisan <[email protected]>
  • Loading branch information
madrisan committed Apr 1, 2024
1 parent 06b5adc commit 746c313
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 117 deletions.
5 changes: 3 additions & 2 deletions include/container.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,12 @@ extern "C"
long long docker_memory_get_total_pgpgout (
struct docker_memory_desc *memdesc);

const char *image_shortname (const char *image);

int docker_running_containers (char *socket, unsigned int *count,
const char *image, char **perfdata,
bool verbose);
int docker_running_containers_memory (char *socket,
long long unsigned int *memory,
char **perfdata, bool verbose);

#ifdef __cplusplus
}
Expand Down
3 changes: 2 additions & 1 deletion include/json_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ extern "C"
int json_token_streq (const char *json, jsmntok_t * tok, const char *s);
char *json_token_tostr (char *json, jsmntok_t * t);
jsmntok_t *json_tokenise (const char *json, size_t *ntoken);
void json_printf_pretty (const char *json, char **dump, int indent);
void json_dump_pretty (const char *json, char **dump, int indent);
int json_search (const char *json, const char *path, char **value);

#ifdef __cplusplus
}
Expand Down
181 changes: 154 additions & 27 deletions lib/container.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,20 @@
#include "url_encode.h"
#include "xalloc.h"
#include "xasprintf.h"
#include "xstrton.h"

/* Hide all jsmn API symbols by making them static */
#define JSMN_STATIC
#include "jsmn.h"

#define DOCKER_CONTAINERS_JSON 0x01
#define DOCKER_STATS_JSON 0x02

/* returns the last portion of the given container image.
example:
"prom/prometheus:v2.39.0" --> "prometheus:v2.39.0"
note that docker just removes the prefix "docker.io/". */
const char *
static const char *
image_shortname (const char *image)
{
const char *strip = strrchr (image, '/');
Expand All @@ -69,9 +71,11 @@ image_shortname (const char *image)
return strip;
}

/* parse the json stream returned by Docker and return a pointer to the
hashtable containing the values of the discovered 'tokens'.
return NULL if the data cannot be parsed. */
/* parse the json stream returned by the Docker/Podman API and return a pointer
to the hashtable containing the values of the discovered 'tokens'.
return NULL if the data cannot be parsed.
the function 'convert' allows a manipulation of the data before saving it in
the hashtable. */

static hashtable_t *
docker_json_parser_search (const char *json, const char *token,
Expand All @@ -94,7 +98,7 @@ docker_json_parser_search (const char *json, const char *token,

dbg ("found token \"%s\" with value \"%s\" at position %d\n",
token, value, buffer[i].start);
counter_put (hashtable, convert (value), increment);
counter_put (hashtable, convert ? convert (value) : value, increment);
free (value);
}
}
Expand Down Expand Up @@ -156,14 +160,15 @@ docker_init (CURL **curl_handle, chunk_t *chunk, const char *socket)
curl_easy_setopt (*curl_handle, CURLOPT_WRITEFUNCTION,
write_memory_callback);
curl_easy_setopt (*curl_handle, CURLOPT_WRITEDATA, (void *) chunk);
curl_easy_setopt (*curl_handle, CURLOPT_NOPROGRESS, 1L);

/* some servers don't like requests that are made without a user-agent
field, so we provide one */
curl_easy_setopt (*curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");
}

static CURLcode
docker_get (CURL *curl_handle, const int query)
docker_get (CURL *curl_handle, const int query, const char *id)
{
CURLcode res;
char *api_version, *class, *url, *filter = NULL;
Expand All @@ -181,18 +186,23 @@ docker_get (CURL *curl_handle, const int query)
plugin_error (STATE_UNKNOWN, 0, "unknown docker query");
case DOCKER_CONTAINERS_JSON:
filter = url_encode ("{\"status\":{\"running\":true}}");
class = "containers/json";
class = xasprintf ("containers/json");
url = filter ?
xasprintf ("http://v%s/%s?filters=%s", api_version, class, filter) :
xasprintf ("http://v%s/%s", api_version, class);
break;
case DOCKER_STATS_JSON:
class = xasprintf ("containers/%s/stats", id);
url = xasprintf ("http://v%s/%s?stream=false", api_version, class);
break;
}

dbg ("docker rest url: %s\n", url);

curl_easy_setopt (curl_handle, CURLOPT_URL, url);
res = curl_easy_perform (curl_handle);

free (class);
free (filter);
free (url);

Expand All @@ -213,47 +223,67 @@ docker_close (CURL *curl_handle, chunk_t *chunk)

#endif /* NPL_TESTING */

/* Returns the number of running Docker containers */

int
docker_running_containers (char *socket, unsigned int *count,
const char *image, char **perfdata, bool verbose)
static int
docker_api_call (char *socket, chunk_t *chunk, CURL *curl_handle,
const char *search_key, bool verbose)
{
chunk_t chunk;
hashtable_t *hashtable;
unsigned int running_containers = 0;

#ifndef NPL_TESTING

CURL *curl_handle = NULL;
CURLcode res;

docker_init (&curl_handle, &chunk, socket);
res = docker_get (curl_handle, DOCKER_CONTAINERS_JSON);
curl_handle = NULL;
docker_init (&curl_handle, chunk, socket);
res = docker_get (curl_handle, DOCKER_CONTAINERS_JSON, NULL);
if (CURLE_OK != res)
{
docker_close (curl_handle, &chunk);
docker_close (curl_handle, chunk);
plugin_error (STATE_UNKNOWN, errno, "%s", curl_easy_strerror (res));
}

#else

int err;
if ((err = docker_get (&chunk, DOCKER_CONTAINERS_JSON)) != 0)
if ((err = docker_get (chunk, DOCKER_CONTAINERS_JSON, NULL)) != 0)
return err;

#endif /* NPL_TESTING */

assert (chunk.memory);
dbg ("%zu bytes retrieved\n", chunk.size);
dbg ("json data: %s", chunk.memory);
assert (chunk->memory);
dbg ("%zu bytes retrieved\n", chunk->size);
dbg ("json data: %s", chunk->memory);

#ifdef ENABLE_DEBUG
char *dump;

json_dump_pretty (chunk->memory, &dump, 1);
dbg ("json data pretty formatted:%s", dump);
free (dump);
#endif

return 0;
}

/* Returns the number of running Docker/Podman containers grouped by images */

int
docker_running_containers (char *socket, unsigned int *count,
const char *image, char **perfdata, bool verbose)
{
chunk_t chunk;
CURL *curl_handle = NULL;

hashtable_t *hashtable;
unsigned int running_containers = 0;
char *search_key = "Image";

docker_api_call (socket, &chunk, curl_handle, search_key, verbose);

hashtable =
docker_json_parser_search (chunk.memory, "Image", image_shortname, 1);
docker_json_parser_search (chunk.memory, search_key, image_shortname, 1);
if (NULL == hashtable)
plugin_error (STATE_UNKNOWN, 0,
"unable to parse the json data for \"Image\"s");
dbg ("number of docker unique images: %u\n",
"unable to parse the json data for \"%s\"s", search_key);
dbg ("number of docker unique containers (by %s): %u\n", search_key,
counter_get_unique_elements (hashtable));

if (image)
Expand Down Expand Up @@ -290,3 +320,100 @@ docker_running_containers (char *socket, unsigned int *count,

return 0;
}

#ifndef NPL_TESTING

/* Returns the memory usage of a Docker/Podman container */
static int
docker_container_memory (char *socket, const char *id,
long long unsigned int *memory, bool verbose)
{
chunk_t chunk;
CURL *curl_handle = NULL;
CURLcode res;
char *errmesg = NULL;
int ret;

docker_init (&curl_handle, &chunk, socket);
res = docker_get (curl_handle, DOCKER_STATS_JSON, id);
if (CURLE_OK != res)
{
docker_close (curl_handle, &chunk);
plugin_error (STATE_UNKNOWN, errno, "%s", curl_easy_strerror (res));
}

assert (chunk.memory);
dbg ("%zu bytes retrieved\n", chunk.size);
dbg ("json data: %s", chunk.memory);

#ifdef ENABLE_DEBUG
/*
char *dump;
json_dump_pretty (chunk.memory, &dump, 1);
dbg ("json data pretty formatted:%s", dump);
free (dump);
*/
#endif

/* get the memory usage */
char *value = NULL;
json_search (chunk.memory, ".memory_stats.usage", &value);

/* FIXME: remove cast */
ret = sizetollint (value, (long long int *)memory, &errmesg);
if (ret < 0)
plugin_error (STATE_UNKNOWN, errno
, "failed to convert container memory value: %s"
, errmesg);

free (value);

return 0;
}

/* Returns the memory usage of running Docker/Podman containers */

int
docker_running_containers_memory (char *socket, long long unsigned int *memory,
char **perfdata, bool verbose)
{
chunk_t chunk;
CURL *curl_handle = NULL;
hashtable_t *hashtable;
char *search_key = "Id";

/* get the list of running containers */
docker_api_call (socket, &chunk, curl_handle, search_key, verbose);

hashtable =
docker_json_parser_search (chunk.memory, search_key, NULL, 1);
if (NULL == hashtable)
plugin_error (STATE_UNKNOWN, 0,
"unable to parse the json data for \"%s\"s", search_key);
dbg ("number of docker containers: %u\n",
counter_get_unique_elements (hashtable));

long long unsigned int memory_usage;
*memory = 0;

/* FIXME: this loop must be threaded and run in parallel */
for (unsigned int j = 0; j < hashtable->uniq; j++)
{
const char *id = hashtable->keys[j];
docker_container_memory (socket, id, &memory_usage, verbose);
dbg ("memory usage for container id \"%s\": %llu\" bytes\n", id,
memory_usage);
*memory += (memory_usage >> 10);
}

docker_close (
#ifndef NPL_TESTING
curl_handle,
#endif
&chunk);

return 0;
}

#endif
48 changes: 47 additions & 1 deletion lib/json_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ json_dump (const char *json, jsmntok_t *t, size_t count, int indent,
}

void
json_printf_pretty (const char *json, char **dump, int indent)
json_dump_pretty (const char *json, char **dump, int indent)
{
size_t ntoken, size;
FILE *stream = open_memstream (dump, &size);
Expand All @@ -143,3 +143,49 @@ json_printf_pretty (const char *json, char **dump, int indent)

fclose (stream);
}

int
json_search (const char *json, const char *path, char **value)
{
char *p, *object, *label, *saveptr;
size_t i, ntoken;
bool found_label = false, found_object = false;

dbg ("checking for %s in the json data...\n", path);
jsmntok_t *tok = json_tokenise (json, &ntoken);

p = xstrdup (path);
object = strtok_r (p, ".", &saveptr);
label = strtok_r (0, ".", &saveptr);

if (object == NULL || label == NULL)
return -1;

dbg ("ckeckin for label \"%s\" in the object \"%s\"\n", label, object);

/* FIXME: the search algorithm sucks :( */
for (i = 0; i < ntoken; i++)
{
if (0 == json_token_streq (json, &tok[i], object))
{
dbg ("found object \"%s\"\n", object);
found_object = true;
}
else if (found_object && (tok[i].type == JSMN_STRING)
&& (0 == json_token_streq (json, &tok[i], label)))
{
dbg ("found label \"%s\"\n", label);
found_label = true;
/* get the associated value */
size_t strsize = tok[i + 1].end - tok[i + 1].start;
*value = xmalloc (strsize + 1);
memcpy (*value, json + tok[i + 1].start, strsize);
break;
}
}
if (found_label && found_object && NULL != *value)
dbg ("found label \"%s\" in the object \"%s\" with value \"%s\"\n", label,
object, *value);

return 0;
}
Loading

0 comments on commit 746c313

Please sign in to comment.