Grant Limberg 8e6e4ede6d
Add prometheus metrics for Central controllers (#1969)
* add header-only prometheus lib to ext

* rename folder

* Undo rename directory

* prometheus simpleapi included on mac & linux

* wip

* wire up some controller stats

* Get windows building with prometheus

* bsd build flags for prometheus

* Fix multiple network join from environment entrypoint.sh.release (#1961)

* _bond_m guards _bond, not _paths_m (#1965)

* Fix: warning: mutex '_aqm_m' is not held on every path through here [-Wthread-safety-analysis] (#1964)

* Serve prom metrics from /metrics endpoint

* Add prom metrics for Central controller specific things

* reorganize metric initialization

* testing out a labled gauge on Networks

* increment error counter on throw

* Consolidate metrics definitions

Put all metric definitions into node/Metrics.hpp.  Accessed as needed
from there.

* Revert "testing out a labled gauge on Networks"

This reverts commit 499ed6d95e11452019cdf48e32ed4cd878c2705b.

* still blows up but adding to the record for completeness right now

* Fix runtime issues with metrics

* Add metrics files to visual studio project

* Missed an "extern"

* add copyright headers to new files

* Add metrics for sent/received bytes (total)

* put /metrics endpoint behind auth

* sendto returns int on Win32

---------

Co-authored-by: Leonardo Amaral <leleobhz@users.noreply.github.com>
Co-authored-by: Brenton Bostick <bostick@gmail.com>
2023-04-21 12:12:43 -07:00

328 lines
8.5 KiB
C++

/*
* httpclientlite.hpp
* ===========================================================================================
*
* The MIT License
*
* Copyright (c) 2016 Christian C. Sachs
* Copyright (c) 2021 Maxim G.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#if defined (__linux__)
# define PLATFORM_LINUX
#elif defined (_WIN32) || defined (_WIN64)
# define PLATFORM_WINDOWS
#else
/* TODO:
* - Added Apple OS */
/* warning: Unknown OS */
#endif
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <cstring>
#include <sstream>
#include <sys/types.h>
#if defined (PLATFORM_WINDOWS)
# include <WinSock2.h>
# include <WS2tcpip.h>
typedef SOCKET socktype_t;
typedef int socklen_t;
# pragma comment(lib, "ws2_32.lib")
#elif defined (PLATFORM_LINUX)
# include <unistd.h>
# include <sys/socket.h>
# include <netdb.h>
# define INVALID_SOCKET -1
# define closesocket(__sock) close(__sock)
typedef int socktype_t;
#endif /* PLATFORM_WINDOWS or PLATFORM_LINUX */
const std::string content_type = "Content-Type: text/plain; version=0.0.4; charset=utf-8";
namespace jdl {
void init_socket() {
#if defined (PLATFORM_WINDOWS)
WSADATA wsa_data;
WSAStartup(MAKEWORD(2, 2), &wsa_data);
#endif /* PLATFORM_WINDOWS */
}
void deinit_socket() {
#if defined (PLATFORM_WINDOWS)
WSACleanup();
#endif /* PLATFORM_WINDOWS */
}
class tokenizer {
public:
inline tokenizer(std::string &str) : str(str), position(0){}
inline std::string next(std::string search, bool returnTail = false) {
size_t hit = str.find(search, position);
if (hit == std::string::npos) {
if (returnTail) {
return tail();
} else {
return "";
}
}
size_t oldPosition = position;
position = hit + search.length();
return str.substr(oldPosition, hit - oldPosition);
}
inline std::string tail() {
size_t oldPosition = position;
position = str.length();
return str.substr(oldPosition);
}
private:
std::string str;
std::size_t position;
};
typedef std::map<std::string, std::string> stringMap;
struct URI {
inline void parseParameters() {
tokenizer qt(querystring);
do {
std::string key = qt.next("=");
if (key == "")
break;
parameters[key] = qt.next("&", true);
} while (true);
}
inline URI(std::string input, bool shouldParseParameters = false) {
tokenizer t = tokenizer(input);
protocol = t.next("://");
std::string hostPortString = t.next("/");
tokenizer hostPort(hostPortString);
host = hostPort.next(hostPortString[0] == '[' ? "]:" : ":", true);
if (host[0] == '[')
host = host.substr(1, host.size() - 1);
port = hostPort.tail();
if (port.empty())
port = "80";
address = t.next("?", true);
querystring = t.next("#", true);
hash = t.tail();
if (shouldParseParameters) {
parseParameters();
}
}
std::string protocol, host, port, address, querystring, hash;
stringMap parameters;
};
struct HTTPResponse {
bool success;
std::string protocol;
std::string response;
std::string responseString;
stringMap header;
std::string body;
inline HTTPResponse() : success(true){}
inline static HTTPResponse fail() {
HTTPResponse result;
result.success = false;
return result;
}
};
struct HTTPClient {
typedef enum {
m_options = 0,
m_get,
m_head,
m_post,
m_put,
m_delete,
m_trace,
m_connect
} HTTPMethod;
inline static const char *method2string(HTTPMethod method) {
const char *methods[] = {"OPTIONS", "GET", "HEAD", "POST", "PUT",
"DELETE", "TRACE", "CONNECT", nullptr};
return methods[method];
}
inline static socktype_t connectToURI(const URI& uri) {
struct addrinfo hints, *result, *rp;
memset(&hints, 0, sizeof(addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
int getaddrinfo_result =
getaddrinfo(uri.host.c_str(), uri.port.c_str(), &hints, &result);
if (getaddrinfo_result != 0)
return -1;
socktype_t fd = INVALID_SOCKET;
for (rp = result; rp != nullptr; rp = rp->ai_next) {
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (fd == INVALID_SOCKET) {
continue;
}
int connect_result = connect(fd, rp->ai_addr, static_cast<socklen_t>(rp->ai_addrlen));
if (connect_result == -1) {
// successfully created a socket, but connection failed. close it!
closesocket(fd);
fd = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(result);
return fd;
}
inline static std::string bufferedRead(socktype_t fd) {
size_t initial_factor = 4, buffer_increment_size = 8192, buffer_size = 0,
bytes_read = 0;
std::string buffer;
buffer.resize(initial_factor * buffer_increment_size);
// do {
bytes_read = recv(fd, ((char*)buffer.c_str()) + buffer_size,
static_cast<socklen_t>(buffer.size() - buffer_size), 0);
buffer_size += bytes_read;
// if (bytes_read > 0 &&
// (buffer.size() - buffer_size) < buffer_increment_size) {
// buffer.resize(buffer.size() + buffer_increment_size);
// }
// } while (bytes_read > 0);
buffer.resize(buffer_size);
return buffer;
}
#define HTTP_NEWLINE "\r\n"
#define HTTP_SPACE " "
#define HTTP_HEADER_SEPARATOR ": "
inline static HTTPResponse request(HTTPMethod method, const URI& uri, const std::string& body = "") {
socktype_t fd = connectToURI(uri);
if (fd < 0)
return HTTPResponse::fail();
// string request = string(method2string(method)) + string(" /") +
// uri.address + ((uri.querystring == "") ? "" : "?") +
// uri.querystring + " HTTP/1.1" HTTP_NEWLINE "Host: " +
// uri.host + HTTP_NEWLINE
// "Accept: */*" HTTP_NEWLINE
// "Connection: close" HTTP_NEWLINE HTTP_NEWLINE;
std::string request = std::string(method2string(method)) + std::string(" /") +
uri.address + ((uri.querystring == "") ? "" : "?") + uri.querystring + " HTTP/1.1" + HTTP_NEWLINE +
"Host: " + uri.host + ":" + uri.port + HTTP_NEWLINE +
"Accept: */*" + HTTP_NEWLINE +
content_type + HTTP_NEWLINE +
"Content-Length: " + std::to_string(body.size()) + HTTP_NEWLINE + HTTP_NEWLINE +
body;
/*int bytes_written = */send(fd, request.c_str(), static_cast<socklen_t>(request.size()), 0);
std::string buffer = bufferedRead(fd);
closesocket(fd);
HTTPResponse result;
tokenizer bt(buffer);
result.protocol = bt.next(HTTP_SPACE);
result.response = bt.next(HTTP_SPACE);
result.responseString = bt.next(HTTP_NEWLINE);
std::string header = bt.next(HTTP_NEWLINE HTTP_NEWLINE);
result.body = bt.tail();
tokenizer ht(header);
do {
std::string key = ht.next(HTTP_HEADER_SEPARATOR);
if (key == "")
break;
result.header[key] = ht.next(HTTP_NEWLINE, true);
} while (true);
return result;
}
};
} /* jdl:: */