Skip to content

Commit

Permalink
Send profiling results by http
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangyi committed Dec 30, 2024
1 parent 432c10f commit 68ac4fe
Show file tree
Hide file tree
Showing 10 changed files with 35,366 additions and 4 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ If you need to profile some code as soon as the JVM starts up, instead of using
it is possible to attach async-profiler as an agent on the command line. For example:

```
$ java -agentpath:/path/to/libasyncProfiler.so=start,event=cpu,file=profile.html ...
$ java -agentpath:/path/to/libasyncProfiler.so=start,file=/tmp/asprof.tmp.jfr,log=/tmp/asprof.log,jfr,loglevel=info,loop=60s,http_out,dd_host=127.0.0.1,dd_port=9529,dd_service=java-profiling-demo,dd_env=testing,dd_version=v0.0.1,event=cpu,alloc,lock ...
```

Agent library is configured through the JVMTI argument interface.
Expand Down
59 changes: 58 additions & 1 deletion src/arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <time.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
#include "arguments.h"


Expand All @@ -19,6 +20,8 @@ Arguments _global_args;
// Predefined value that denotes successful operation
const Error Error::OK(NULL);

const char *MSG_EMPTY_FILE = "file must not be empty";

// Extra buffer space for expanding file pattern
const size_t EXTRA_BUF_SIZE = 512;

Expand Down Expand Up @@ -278,7 +281,7 @@ Error Arguments::parse(const char* args) {

CASE("file")
if (value == NULL || value[0] == 0) {
msg = "file must not be empty";
msg = MSG_EMPTY_FILE;
}
_file = value;

Expand Down Expand Up @@ -389,11 +392,34 @@ Error Arguments::parse(const char* args) {
CASE("reverse")
_reverse = true;

// send profile by http
CASE("http_out")
_http_out = true;

CASE("dd_host")
_dd_agent_host = value;

CASE("dd_port")
_dd_trace_agent_port = atoi(value);

CASE("dd_service")
_dd_service = value;

CASE("dd_env")
_dd_env = value;

CASE("dd_version")
_dd_version = value;

DEFAULT()
if (_unknown_arg == NULL) _unknown_arg = arg;
}
}

if (msg == MSG_EMPTY_FILE && _http_out) {
msg = NULL;
}

// Return error only after parsing all arguments, when 'log' is already set
if (msg != NULL) {
return Error(msg);
Expand All @@ -419,13 +445,42 @@ Error Arguments::parse(const char* args) {
return Error::OK;
}

const char* Arguments::getTempDir() {
char *tmpDir = std::getenv("TMPDIR");
if (tmpDir != nullptr && tmpDir[0] != 0) {
const size_t len = strlen(tmpDir);
if (tmpDir[len - 1] == '/') {
tmpDir[len - 1] = 0;
}
return tmpDir;
}
return "/tmp";
}

const char* Arguments::file() {
if (_file == NULL && _http_out) {
_temp_filename = (char *)calloc(2048, sizeof(char));
snprintf(_temp_filename, 2048, "%s/asprof-%d.jfr", getTempDir(), getpid());
_file = _temp_filename;
}
if (_file != NULL && strchr(_file, '%') != NULL) {
return expandFilePattern(_file);
}
return _file;
}

httplib::Client* Arguments::httpClient() {
if (_httpcli == NULL) {
_httpcli = new httplib::Client(_dd_agent_host, _dd_trace_agent_port);
_httpcli->set_keep_alive(true);
_httpcli->set_follow_location(true);
_httpcli->set_connection_timeout(3, 0);
_httpcli->set_read_timeout(5, 0);
_httpcli->set_write_timeout(8, 0);
}
return _httpcli;
}

// Returns true if the log file is a temporary file of asprof launcher
bool Arguments::hasTemporaryLog() const {
return _log != NULL && strncmp(_log, "/tmp/asprof-log.", 16) == 0;
Expand Down Expand Up @@ -557,6 +612,8 @@ int Arguments::parseTimeout(const char* str) {

Arguments::~Arguments() {
if (!_shared) free(_buf);
if (_temp_filename != NULL) free(_temp_filename);
delete _httpcli;
}

void Arguments::save() {
Expand Down
25 changes: 24 additions & 1 deletion src/arguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
#ifndef _ARGUMENTS_H
#define _ARGUMENTS_H


#include <stddef.h>
#define CPPHTTPLIB_NO_EXCEPTIONS
#include "httplib.h"


const long DEFAULT_INTERVAL = 10000000; // 10 ms
Expand Down Expand Up @@ -143,6 +146,7 @@ class Arguments {
private:
char* _buf;
bool _shared;
httplib::Client* _httpcli;

void appendToEmbeddedList(int& list, char* value);
const char* expandFilePattern(const char* pattern);
Expand Down Expand Up @@ -199,6 +203,13 @@ class Arguments {
const char* _title;
double _minwidth;
bool _reverse;
char *_temp_filename;
bool _http_out;
const char *_dd_agent_host;
int _dd_trace_agent_port;
const char *_dd_service;
const char *_dd_env;
const char *_dd_version;

Arguments() :
_buf(NULL),
Expand Down Expand Up @@ -247,7 +258,15 @@ class Arguments {
_end(NULL),
_title(NULL),
_minwidth(0),
_reverse(false) {
_reverse(false),
_temp_filename(NULL),
_dd_agent_host("127.0.0.1"),
_dd_trace_agent_port(9529),
_dd_service(""),
_dd_env(""),
_dd_version(""),
_httpcli(NULL),
_http_out(false) {
}

~Arguments();
Expand All @@ -256,8 +275,12 @@ class Arguments {

Error parse(const char* args);

const char* getTempDir();

const char* file();

httplib::Client* httpClient();

bool hasTemporaryLog() const;

bool hasOutputFile() const {
Expand Down
58 changes: 58 additions & 0 deletions src/flightRecorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sstream>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
Expand All @@ -26,6 +27,8 @@
#include "threadFilter.h"
#include "tsc.h"
#include "vmStructs.h"
#include "nlohmann/json.hpp"
#include "httplib.h"


INCBIN(JFR_SYNC_CLASS, "src/helper/one/profiler/JfrSync.class")
Expand Down Expand Up @@ -503,6 +506,57 @@ class Recording {
free(_master_recording_file);
}

if (_global_args._http_out) {
off_t size = lseek(_fd, 0, SEEK_SET);
std::string jfr;
jfr.reserve(size);
char buf[8192];
ssize_t rlen;
while(true) {
rlen = read(_fd, buf, sizeof(buf));
if (rlen <= 0) {
break;
}
jfr.append(buf, rlen);
}

nlohmann::json j;
j["format"] = "jfr";
j["profiler"] = "async-profiler";
j["attachments"] = nlohmann::json::array({"main.jfr"});
j["language"] = "jvm";
std::stringstream ss;
// "process_id:31145,service:zy-profiling-test,profiler_version:0.102.0~b67f6e3380,host:zydeMacBook-Air.local,runtime-id:06dddda1-957b-4619-97cb-1a78fc7e3f07,language:jvm,env:test,version:v1.2"
char hostname[512] = {};
gethostname(hostname, sizeof(hostname));
ss << "process_id:" << getpid() << ",host:" << hostname << ",profiler_version:" << PROFILER_VERSION << ",service:" << _global_args._dd_service;
ss << ",env:" << _global_args._dd_env << ",version:" << _global_args._dd_version << ",language:jvm";
j["tags_profiler"] = ss.str();
time_t start_time = Profiler::instance()->start_time();
time_t stop_time = Profiler::instance()->stop_time();
char buffer[40] = {};
strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", gmtime(&start_time));
j["start"] = std::string(buffer);

strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", gmtime(&stop_time));
j["end"] = std::string(buffer);

httplib::MultipartFormDataItems form = {
{"event", j.dump(), "event.json", "application/octet-stream"},
{"main", jfr, "main.jfr", "application/octet-stream"},
};
httplib::Client* cli = _global_args.httpClient();
httplib::Result result;
for (int i = 0; i < 3; i++) {
result = cli->Post("/profiling/v1/input", form);
if (result->status / 100 == 2) {
break;
} else {
Log::warn("unable to send profile file by http, status: %d, response: %s", result->status, result->body.c_str());
}
}
}

close(_fd);
}

Expand Down Expand Up @@ -1299,6 +1353,10 @@ Error FlightRecorder::start(Arguments& args, bool reset) {
free(filename_tmp);
}

if (args._http_out) {
unlink(filename);
}

_rec = new Recording(fd, master_recording_file, args);
_rec_lock.unlock();
return Error::OK;
Expand Down
Loading

0 comments on commit 68ac4fe

Please sign in to comment.