Skip to content
Permalink
master
Switch branches/tags
Go to file
@apangin
Latest commit 0287513 Mar 3, 2021 History
4 contributors

Users who have contributed to this file

@apangin @rpulle @nitsanw @jpbempel
392 lines (322 sloc) 12.6 KB
/*
* Copyright 2017 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <unistd.h>
#include "arguments.h"
// Predefined value that denotes successful operation
const Error Error::OK(NULL);
// Extra buffer space for expanding file pattern
const size_t EXTRA_BUF_SIZE = 512;
// Statically compute hash code of a string containing up to 12 [a-z] letters
#define HASH(s) ((s[0] & 31LL) | (s[1] & 31LL) << 5 | (s[2] & 31LL) << 10 | (s[3] & 31LL) << 15 | \
(s[4] & 31LL) << 20 | (s[5] & 31LL) << 25 | (s[6] & 31LL) << 30 | (s[7] & 31LL) << 35 | \
(s[8] & 31LL) << 40 | (s[9] & 31LL) << 45 | (s[10] & 31LL) << 50 | (s[11] & 31LL) << 55)
// Simulate switch statement over string hashes
#define SWITCH(arg) long long arg_hash = hash(arg); if (0)
#define CASE(s) } else if (arg_hash == HASH(s " ")) {
// Parses agent arguments.
// The format of the string is:
// arg[,arg...]
// where arg is one of the following options:
// start - start profiling
// resume - start or resume profiling without resetting collected data
// stop - stop profiling
// check - check if the specified profiling event is available
// status - print profiling status (inactive / running for X seconds)
// list - show the list of available profiling events
// version[=full] - display the agent version
// event=EVENT - which event to trace (cpu, wall, cache-misses, etc.)
// alloc[=BYTES] - profile allocations with BYTES interval
// lock[=DURATION] - profile contended locks longer than DURATION ns
// collapsed - dump collapsed stacks (the format used by FlameGraph script)
// flamegraph - produce Flame Graph in HTML format
// tree - produce call tree in HTML format
// jfr - dump events in Java Flight Recorder format
// flat[=N] - dump top N methods (aka flat profile)
// samples - count the number of samples (default)
// total - count the total value (time, bytes, etc.) instead of samples"
// interval=N - sampling interval in ns (default: 10'000'000, i.e. 10 ms)
// jstackdepth=N - maximum Java stack depth (default: 2048)
// safemode=BITS - disable stack recovery techniques (default: 0, i.e. everything enabled)
// file=FILENAME - output file name for dumping
// log=FILENAME - log warnings and errors to the given dedicated stream
// filter=FILTER - thread filter
// threads - profile different threads separately
// cstack=MODE - how to collect C stack frames in addition to Java stack
// MODE is 'fp' (Frame Pointer), 'lbr' (Last Branch Record) or 'no'
// allkernel - include only kernel-mode events
// alluser - include only user-mode events
// simple - simple class names instead of FQN
// dot - dotted class names
// sig - print method signatures
// ann - annotate Java method names
// include=PATTERN - include stack traces containing PATTERN
// exclude=PATTERN - exclude stack traces containing PATTERN
// begin=FUNCTION - begin profiling when FUNCTION is executed
// end=FUNCTION - end profiling when FUNCTION is executed
// title=TITLE - FlameGraph title
// minwidth=PCT - FlameGraph minimum frame width in percent
// reverse - generate stack-reversed FlameGraph / Call tree
//
// It is possible to specify multiple dump options at the same time
Error Arguments::parse(const char* args) {
if (args == NULL) {
return Error::OK;
}
size_t len = strlen(args);
free(_buf);
_buf = (char*)malloc(len + EXTRA_BUF_SIZE);
if (_buf == NULL) {
return Error("Not enough memory to parse arguments");
}
strcpy(_buf, args);
const char* msg = NULL;
for (char* arg = strtok(_buf, ","); arg != NULL; arg = strtok(NULL, ",")) {
char* value = strchr(arg, '=');
if (value != NULL) *value++ = 0;
SWITCH (arg) {
// Actions
CASE("start")
_action = ACTION_START;
CASE("resume")
_action = ACTION_RESUME;
CASE("stop")
_action = ACTION_STOP;
CASE("check")
_action = ACTION_CHECK;
CASE("status")
_action = ACTION_STATUS;
CASE("list")
_action = ACTION_LIST;
CASE("version")
_action = value == NULL ? ACTION_VERSION : ACTION_FULL_VERSION;
// Output formats
CASE("collapsed")
_output = OUTPUT_COLLAPSED;
CASE("flamegraph")
_output = OUTPUT_FLAMEGRAPH;
CASE("tree")
_output = OUTPUT_TREE;
CASE("jfr")
_output = OUTPUT_JFR;
CASE("traces")
_output = OUTPUT_TEXT;
_dump_traces = value == NULL ? INT_MAX : atoi(value);
CASE("flat")
_output = OUTPUT_TEXT;
_dump_flat = value == NULL ? INT_MAX : atoi(value);
CASE("samples")
_counter = COUNTER_SAMPLES;
CASE("total")
_counter = COUNTER_TOTAL;
// Basic options
CASE("event")
if (value == NULL || value[0] == 0) {
msg = "event must not be empty";
} else if (strcmp(value, EVENT_ALLOC) == 0) {
if (_alloc <= 0) _alloc = 1;
} else if (strcmp(value, EVENT_LOCK) == 0) {
if (_lock <= 0) _lock = 1;
} else if (_event != NULL) {
msg = "Duplicate event argument";
} else {
_event = value;
}
CASE("alloc")
_alloc = value == NULL ? 1 : parseUnits(value);
if (_alloc < 0) {
msg = "alloc must be >= 0";
}
CASE("lock")
_lock = value == NULL ? 1 : parseUnits(value);
if (_lock < 0) {
msg = "lock must be >= 0";
}
CASE("interval")
if (value == NULL || (_interval = parseUnits(value)) <= 0) {
msg = "Invalid interval";
}
CASE("jstackdepth")
if (value == NULL || (_jstackdepth = atoi(value)) <= 0) {
msg = "jstackdepth must be > 0";
}
CASE("safemode")
_safe_mode = value == NULL ? INT_MAX : atoi(value);
CASE("file")
if (value == NULL || value[0] == 0) {
msg = "file must not be empty";
}
_file = value;
CASE("log")
_log = value == NULL || value[0] == 0 ? NULL : value;
// Filters
CASE("filter")
_filter = value == NULL ? "" : value;
CASE("include")
if (value != NULL) appendToEmbeddedList(_include, value);
CASE("exclude")
if (value != NULL) appendToEmbeddedList(_exclude, value);
CASE("threads")
_threads = true;
CASE("allkernel")
_ring = RING_KERNEL;
CASE("alluser")
_ring = RING_USER;
CASE("cstack")
if (value != NULL) {
if (value[0] == 'n') {
_cstack = CSTACK_NO;
} else if (value[0] == 'l') {
_cstack = CSTACK_LBR;
} else {
_cstack = CSTACK_FP;
}
}
// Output style modifiers
CASE("simple")
_style |= STYLE_SIMPLE;
CASE("dot")
_style |= STYLE_DOTTED;
CASE("sig")
_style |= STYLE_SIGNATURES;
CASE("ann")
_style |= STYLE_ANNOTATE;
CASE("begin")
_begin = value;
CASE("end")
_end = value;
// FlameGraph options
CASE("title")
if (value != NULL) _title = value;
CASE("minwidth")
if (value != NULL) _minwidth = atof(value);
CASE("reverse")
_reverse = true;
}
}
// Return error only after parsing all arguments, when 'log' is already set
if (msg != NULL) {
return Error(msg);
}
if (_event == NULL && _alloc == 0 && _lock == 0) {
_event = EVENT_CPU;
}
if (_file != NULL && strchr(_file, '%') != NULL) {
_file = expandFilePattern(_buf + len + 1, EXTRA_BUF_SIZE - 1, _file);
}
if (_file != NULL && _output == OUTPUT_NONE) {
_output = detectOutputFormat(_file);
if (_output == OUTPUT_SVG) {
return Error("SVG format is obsolete, use .html for FlameGraph");
}
_dump_traces = 100;
_dump_flat = 200;
}
if (_output != OUTPUT_NONE && (_action == ACTION_NONE || _action == ACTION_STOP)) {
_action = ACTION_DUMP;
}
return Error::OK;
}
bool Arguments::hasOutputFile() const {
return _file != NULL && (_action == ACTION_DUMP ? _output != OUTPUT_JFR : _action >= ACTION_STATUS);
}
// The linked list of string offsets is embedded right into _buf array
void Arguments::appendToEmbeddedList(int& list, char* value) {
((int*)value)[-1] = list;
list = (int)(value - _buf);
}
// Should match statically computed HASH(arg)
long long Arguments::hash(const char* arg) {
long long h = 0;
for (int shift = 0; *arg != 0; shift += 5) {
h |= (*arg++ & 31LL) << shift;
}
return h;
}
// Expands %p to the process id
// %t to the timestamp
const char* Arguments::expandFilePattern(char* dest, size_t max_size, const char* pattern) {
char* ptr = dest;
char* end = dest + max_size - 1;
while (ptr < end && *pattern != 0) {
char c = *pattern++;
if (c == '%') {
c = *pattern++;
if (c == 0) {
break;
} else if (c == 'p') {
ptr += snprintf(ptr, end - ptr, "%d", getpid());
continue;
} else if (c == 't') {
time_t timestamp = time(NULL);
struct tm t;
localtime_r(&timestamp, &t);
ptr += snprintf(ptr, end - ptr, "%d%02d%02d-%02d%02d%02d",
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
t.tm_hour, t.tm_min, t.tm_sec);
continue;
}
}
*ptr++ = c;
}
*ptr = 0;
return dest;
}
Output Arguments::detectOutputFormat(const char* file) {
const char* ext = strrchr(file, '.');
if (ext != NULL) {
if (strcmp(ext, ".html") == 0) {
return OUTPUT_FLAMEGRAPH;
} else if (strcmp(ext, ".jfr") == 0) {
return OUTPUT_JFR;
} else if (strcmp(ext, ".collapsed") == 0 || strcmp(ext, ".folded") == 0) {
return OUTPUT_COLLAPSED;
} else if (strcmp(ext, ".svg") == 0) {
return OUTPUT_SVG;
}
}
return OUTPUT_TEXT;
}
long Arguments::parseUnits(const char* str) {
char* end;
long result = strtol(str, &end, 0);
switch (*end) {
case 0:
return result;
case 'K': case 'k':
case 'U': case 'u': // microseconds
return result * 1000;
case 'M': case 'm': // million, megabytes or milliseconds
return result * 1000000;
case 'G': case 'g':
case 'S': case 's': // seconds
return result * 1000000000;
}
return -1;
}
Arguments::~Arguments() {
if (!_shared) free(_buf);
}
void Arguments::save(Arguments& other) {
if (!_shared) free(_buf);
*this = other;
other._shared = true;
}