Skip to content

Commit b8be513

Browse files
alexandrefimovImpala Public Jenkins
authored andcommitted
IMPALA-13033: Parse WebUI thrift profile downloads
WebUI thrift profile downloads contain the archived profile string without the timestamp and query id prefix used by profile log lines. impala-profile-tool currently treats these files as malformed input. Retry failed log-line parses as bare archived profile input while keeping query id and timestamp filters limited to log entries that include metadata. Add regression coverage for bare profile input using the existing public profile fixtures. Generated-by: OpenAI Codex Change-Id: I2baed77cce3de43ef153b853329ad899f999f473 Reviewed-on: http://gerrit.cloudera.org:8080/24357 Reviewed-by: Impala Public Jenkins <impala-public-jenkins@cloudera.com> Tested-by: Impala Public Jenkins <impala-public-jenkins@cloudera.com>
1 parent 70aa5f5 commit b8be513

2 files changed

Lines changed: 67 additions & 19 deletions

File tree

be/src/util/impala-profile-tool.cc

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,22 @@
2525
#include <gflags/gflags.h>
2626

2727
#include "common/object-pool.h"
28+
#include "gutil/strings/strip.h"
2829
#include "util/os-info.h"
2930
#include "util/runtime-profile.h"
3031

3132
#include "common/names.h"
3233

3334
static const char* USAGE =
34-
"Utility to decode an Impala profile log from standard input.\n"
35+
"Utility to decode an Impala profile log or WebUI thrift profile download"
36+
" from standard input.\n"
3537
"\n"
36-
"The profile log is consumed from standard input and each successfully parsed entry"
38+
"The input is consumed from standard input and each successfully parsed profile"
3739
" is pretty-printed to standard output.\n"
3840
"\n"
39-
"Usage:"
41+
"Usage:\n"
4042
" impala-profile-tool < impala_profile_log_1.1-1607057366897\n"
43+
" impala-profile-tool < thrift_profile_<query id>.txt\n"
4144
"\n"
4245
"The following options are supported:\n"
4346
"Output options:\n"
@@ -54,7 +57,9 @@ static const char* USAGE =
5457
"--min_timestamp=<integer timestamp>: only process profiles at or after this"
5558
" timestamp\n"
5659
"--max_timestamp=<integer timestamp>: only process profiles at or before this"
57-
" timestamp\n";
60+
" timestamp\n"
61+
"Filtering options only apply to profile log entries that include timestamp and"
62+
" query id metadata.\n";
5863

5964
DEFINE_string(
6065
profile_format, "text", "Profile format to output: either text, json or prettyjson");
@@ -104,23 +109,25 @@ int main(int argc, char** argv) {
104109
int profiles_emitted = 0;
105110
string line;
106111
int lineno = 1;
107-
// Read profile log lines from stdin.
112+
// Read profile log or WebUI thrift profile lines from stdin.
108113
for (; getline(cin, line); ++lineno) {
109-
// Parse out fields from the line.
114+
// Profile logs prefix each encoded profile with timestamp and query id. WebUI
115+
// thrift profile downloads contain only the encoded profile.
110116
istringstream liness(line);
111-
int64_t timestamp;
117+
int64_t timestamp = -1;
112118
string query_id, encoded_profile;
113119
liness >> timestamp >> query_id >> encoded_profile;
114-
if (liness.fail()) {
115-
cerr << "Error parsing line " << lineno << ": '" << line << "'\n";
116-
++errors;
117-
continue;
120+
const bool has_log_metadata = !liness.fail();
121+
if (!has_log_metadata) {
122+
encoded_profile = line;
123+
StripWhiteSpace(&encoded_profile);
118124
}
119125

120126
// Skip decoding entries that don't match our parameters.
121-
if ((FLAGS_query_id != "" && FLAGS_query_id != query_id) ||
122-
(FLAGS_min_timestamp != -1 && timestamp < FLAGS_min_timestamp) ||
123-
(FLAGS_max_timestamp != -1 && timestamp > FLAGS_max_timestamp)) {
127+
if (has_log_metadata
128+
&& ((FLAGS_query_id != "" && FLAGS_query_id != query_id) ||
129+
(FLAGS_min_timestamp != -1 && timestamp < FLAGS_min_timestamp) ||
130+
(FLAGS_max_timestamp != -1 && timestamp > FLAGS_max_timestamp))) {
124131
continue;
125132
}
126133

tests/observability/test_profile_tool.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,53 @@ def test_json_output_profile_v2(self):
8080
get_profile_path(
8181
'impala_profile_log_tpcds_compute_stats_v2_extended.expected.pretty.json'))
8282

83+
def test_webui_thrift_profile_text_output(self):
84+
# WebUI thrift profile downloads contain only the archived profile string, without
85+
# the timestamp and query id prefix found in profile log lines.
86+
self._compare_webui_thrift_profile_output([],
87+
get_profile_path('impala_profile_log_tpcds_compute_stats'))
88+
self._compare_webui_thrift_profile_output([],
89+
get_profile_path('impala_profile_log_tpcds_compute_stats_v2'))
90+
91+
def test_webui_thrift_profile_prettyjson_output(self):
92+
self._compare_webui_thrift_profile_output(['--profile_format=prettyjson'],
93+
get_profile_path('impala_profile_log_tpcds_compute_stats'))
94+
95+
def test_webui_thrift_profile_ignores_surrounding_whitespace(self):
96+
self._compare_webui_thrift_profile_output([],
97+
get_profile_path('impala_profile_log_tpcds_compute_stats'),
98+
profile_prefix=' \t', profile_suffix=' \r')
99+
83100
def _compare_profile_tool_output(self, args, input_log, expected_output):
84101
"""Run impala-profile-tool on input_log and compare it to the contents of the
85102
file at 'expected_output'."""
86103
with tempfile.NamedTemporaryFile() as tmp:
87-
with open(input_log, 'r') as f:
88-
check_call([os.path.join(IMPALA_HOME, "bin/run-binary.sh"),
89-
os.path.join(impalad_basedir, 'util/impala-profile-tool')] + args,
90-
stdin=f, stdout=tmp)
91-
check_call(['diff', expected_output, tmp.name])
104+
self._run_profile_tool(args, input_log, tmp)
105+
check_call(['diff', expected_output, tmp.name])
106+
107+
def _compare_webui_thrift_profile_output(
108+
self, args, input_log, profile_prefix='', profile_suffix=''):
109+
"""Compare a bare WebUI thrift profile to the same profile in a profile log."""
110+
query_id, encoded_profile = self._get_first_profile_log_entry(input_log)
111+
with tempfile.NamedTemporaryFile() as thrift_profile:
112+
with tempfile.NamedTemporaryFile() as expected_output:
113+
with tempfile.NamedTemporaryFile() as actual_output:
114+
profile_input = profile_prefix + encoded_profile + profile_suffix + '\n'
115+
thrift_profile.write(profile_input.encode('utf-8'))
116+
thrift_profile.flush()
117+
self._run_profile_tool(
118+
args + ['--query_id=%s' % query_id], input_log, expected_output)
119+
self._run_profile_tool(args, thrift_profile.name, actual_output)
120+
check_call(['diff', expected_output.name, actual_output.name])
121+
122+
def _get_first_profile_log_entry(self, input_log):
123+
with open(input_log, 'r') as f:
124+
_timestamp, query_id, encoded_profile = f.readline().split(None, 2)
125+
return query_id, encoded_profile.rstrip()
126+
127+
def _run_profile_tool(self, args, input_log, output=None):
128+
with open(input_log, 'r') as f:
129+
command = [os.path.join(IMPALA_HOME, "bin/run-binary.sh"),
130+
os.path.join(impalad_basedir, 'util/impala-profile-tool')] + args
131+
check_call(command, stdin=f, stdout=output)
132+
output.flush()

0 commit comments

Comments
 (0)