Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: initial prototype health file util
  • Loading branch information
bduranleau-nr committed Mar 26, 2025
commit 9114ac46e54fcdb755b505c424eda33bb38a350f
1 change: 1 addition & 0 deletions axiom/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ OBJS := \
util_hashmap.o \
util_json.o \
util_logging.o \
util_health.o \
util_labels.o \
util_matcher.o \
util_md5.o \
Expand Down
243 changes: 243 additions & 0 deletions axiom/util_health.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

#include "nr_axiom.h"

#include <sys/time.h>

#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <time.h>

#include "util_health.h"
#include "php_globals.h"
#include "util_memory.h"
#include "util_strings.h"
#include "util_syscalls.h"
#include "util_logging.h"

#define BILLION (1000000000L)

typedef struct _nrh_status_codes_t {
const char* code;
const char* description;
} nrh_status_codes_t;

// clang-format off
static nrh_status_codes_t health_statuses[NRH_MAX_STATUS] = {
{"NR-APM-000", "Healthy"},
{"NR-APM-001", "Invalid license key"},
{"NR-APM-002", "License Key missing in configuration"},
{"NR-APM-003", "Forced disconnect received from New Relic"},
{"NR-APM-004", "HTTP error response code [%s] received from New Relic while sending data type [%s]"},
{"NR-APM-005", "Missing application name in agent configuration"},
{"NR-APM-006", "The maximum number of configured app names (3) exceeded"},
{"NR-APM-007", "HTTP Proxy configuration error, response code [%s]"},
{"NR-APM-008", "Agent is disabled via configuration"},
{"NR-APM-009", "Failed to connect to New Relic data collector"},
{"NR-APM-010", "Agent config file is not able to be parsed"},
{"NR-APM-099", "Agent has shutdown"}
};
// clang-format on

static int healthfile_fd = -1;
static struct timespec start_time = {0, 0};
static nrhealth_t last_error_code = NRH_HEALTHY;

static char* nrh_get_uuid(void) {
// TODO: UUID generation logic
return nr_strdup("bc21b5891f5e44fc9272caef924611a8");
}

char* nrh_strip_scheme_prefix(char* uri) {
char* filedir = NULL;
int prefix_len = nr_strlen("file://");
int uri_len = nr_strlen(uri);

if (uri_len <= prefix_len) {
// uri must contain more than just the scheme information.
nrl_warning(NRL_AGENT, "%s: invalid uri %s", __func__, uri);
return NULL;
}

if (!nr_strstr(uri, "file://")) {
// missing uri scheme, undefined behavior. treat as error.
nrl_warning(NRL_AGENT, "%s: invalid uri %s", __func__, uri);
return NULL;
}

// allocate space for stripped string + null terminator.
filedir = (char*)nr_malloc(uri_len - prefix_len + 1);

// copy string starting at uri path offset.
nr_strcpy(filedir, uri + prefix_len);

return filedir;
}

nr_status_t nrh_set_health_location(char* uri) {
char* filedir = NULL;
struct stat statbuf;

nr_free(NR_PHP_PROCESS_GLOBALS(agent_control_health_location));

if (NULL == uri || 0 == uri[0]) {
return NR_FAILURE;
}

filedir = nrh_strip_scheme_prefix(uri);

if (NULL == filedir) {
return NR_FAILURE;
}

if (0 != nr_stat(filedir, &statbuf)) {
nr_free(filedir);
return NR_FAILURE;
}

if (0 == S_ISDIR(statbuf.st_mode)) {
nr_free(filedir);
return NR_FAILURE;
}

NR_PHP_PROCESS_GLOBALS(agent_control_health_location) = nr_strdup(filedir);

nr_free(filedir);
return NR_SUCCESS;
}

nr_status_t nrh_set_health_file(char* uri) {
char* uuid = NULL;
char* filedir = NULL;
char* filepath = NULL;
struct stat statbuf;

if (NULL == uri || 0 == uri[0]) {
nrl_warning(NRL_AGENT, "%s: uri is NULL", __func__);
goto fail;
}

filedir = nrh_strip_scheme_prefix(uri);

if (NULL == filedir) {
nrl_warning(NRL_AGENT, "%s: filedir is NULL", __func__);
goto fail;
}

if (0 != nr_stat(filedir, &statbuf)) {
nrl_warning(NRL_AGENT, "%s: filedir %s innacessible", __func__, filedir);
goto fail;
}

if (0 == S_ISDIR(statbuf.st_mode)) {
nrl_warning(NRL_AGENT, "%s: filedir %s is not a directory", __func__,
filedir);
goto fail;
}

uuid = nrh_get_uuid();

filepath = nr_formatf("%s/health-%s.yml", filedir, uuid);

healthfile_fd = nr_open(filepath, O_WRONLY | O_APPEND | O_CREAT, 0666);

nr_free(uuid);
nr_free(filepath);
nr_free(filedir);

return NR_SUCCESS;

fail:
nr_free(uuid);
nr_free(filepath);
nr_free(filedir);

return NR_FAILURE;
}

void nrh_close_health_file(void) {
if (-1 == healthfile_fd) {
return;
}

nr_close(healthfile_fd);
healthfile_fd = -1;
}

nr_status_t nrh_set_start_time(void) {
clock_gettime(CLOCK_REALTIME, &start_time);

if (0 == start_time.tv_nsec) {
return NR_FAILURE;
}

return NR_SUCCESS;
}

long long nrh_get_start_time_ns(void) {
return (long long)(start_time.tv_sec * BILLION + start_time.tv_nsec);
}

long long nrh_get_current_time_ns(void) {
struct timespec ts;

clock_gettime(CLOCK_REALTIME, &ts);

return (long long)(ts.tv_sec * BILLION + ts.tv_nsec);
}

int nrh_get_healthfile_fd(void) {
return healthfile_fd;
}

nr_status_t nrh_set_last_error(nrhealth_t status) {
if (status < NRH_HEALTHY || status >= NRH_MAX_STATUS) {
return NR_FAILURE;
}

if (NRH_SHUTDOWN == status && NRH_HEALTHY != last_error_code) {
// cannot report shutdown if agent is unhealthy
return NR_FAILURE;
}

last_error_code = status;
return NR_SUCCESS;
}

nrhealth_t nrh_get_last_error(void) {
return last_error_code;
}

#ifndef HEALTH_STATUS_LINE
#define HEALTH_STATUS_LINE(field, value) \
nr_write(healthfile_fd, nr_formatf("%s: %s\n", field, value), \
nr_strlen(nr_formatf("%s: %s\n", field, value)));

nr_status_t nrh_write_health(void) {
nrhealth_t status = last_error_code;

if (-1 == healthfile_fd) {
// healthfile not initialized
return NR_FAILURE;
}

HEALTH_STATUS_LINE("healthy", NRH_HEALTHY == status ? "true" : "false");

HEALTH_STATUS_LINE("status", health_statuses[status].description);

HEALTH_STATUS_LINE("last_error_code", health_statuses[status].code);

HEALTH_STATUS_LINE("status_time_unix_nano",
nr_formatf("%lld", nrh_get_current_time_ns()));

HEALTH_STATUS_LINE("start_time_unix_nano",
nr_formatf("%lld", nrh_get_start_time_ns()));

return NR_SUCCESS;
}
#undef HEALTH_STATUS_LINE
#endif
44 changes: 44 additions & 0 deletions axiom/util_health.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/*
* This file contains functions for agent control health file handling.
*/
#ifndef UTIL_HEALTH_HDR
#define UTIL_HEALTH_HDR

#include <sys/time.h>

#include "nr_axiom.h"

typedef enum _nrhealth_t {
NRH_HEALTHY = 0,
NRH_INVALID_LICENSE,
NRH_MISSING_LICENSE,
NRH_FORCED_DISCONNECT,
NRH_HTTP_ERROR,
NRH_MISSING_APPNAME,
NRH_MAX_APPNAME,
NRH_PROXY_ERROR,
NRH_AGENT_DISABLED,
NRH_CONNECTION_FAILED,
NRH_CONFIG_ERROR,
NRH_SHUTDOWN,
NRH_MAX_STATUS
} nrhealth_t;

extern char* nrh_strip_scheme_prefix(char* uri);
extern nr_status_t nrh_set_health_location(char* uri);
extern nr_status_t nrh_set_health_file(char* filename);
extern void nrh_close_health_file(void);
extern nr_status_t nrh_set_start_time(void);
extern long long nrh_get_start_time_ns(void);
extern long long nrh_get_current_time_ns(void);
extern int nrh_get_healthfile_fd(void);
extern nr_status_t nrh_set_last_error(nrhealth_t code);
extern nrhealth_t nrh_get_last_error(void);
extern nr_status_t nrh_write_health(void);

#endif /* UTIL_HEALTH_HDR */