Skip to content
This repository was archived by the owner on Feb 29, 2024. It is now read-only.

Commit d0bd037

Browse files
committed
Update Java Cloud Debugger to v2.2
1 parent 86858e9 commit d0bd037

29 files changed

+564
-196
lines changed

README.md

Lines changed: 170 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,181 @@
11
# Java Cloud Debugger
22

3+
Google [Cloud Debugger](https://cloud.google.com/tools/cloud-debugger/) for
4+
Java.
5+
6+
## Overview
7+
38
The Cloud Debugger lets you inspect the state of a Java application at any code
49
location without stopping or slowing it down. The debugger makes it easier to
510
view the application state without adding logging statements.
611

712
You can use the Cloud Debugger on both production and staging instances of your
813
application. The debugger adds less than 10ms to the request latency only when
914
the application state is captured. In most cases, this is not noticeable by
10-
users.
15+
users. The Cloud Debugger gives a read-only experience. Application variables
16+
can't be changed through the debugger.
17+
18+
The Cloud Debugger attaches to all instances of the application. The call stack
19+
and the variables come from the first instance to take the snapshot.
20+
21+
The Java Cloud Debugger is only supported on Linux at the moment. It was tested
22+
on Debian Linux, but it should work on other distributions as well.
23+
24+
The Cloud Debugger consists of 3 primary components:
25+
26+
1. The debugger agent (requires Java 7 and above).
27+
2. Cloud Debugger backend that stores the list of snapshots for each
28+
application. You can explore the API using the
29+
[APIs Explorer](https://developers.google.com/apis-explorer/#p/clouddebugger/v2/).
30+
3. User interface for the debugger implemented using the Cloud Debugger API.
31+
Currently the only option for Java is the
32+
[Google Developers Console](https://console.developers.google.com). The
33+
UI requires that the source code is submitted to
34+
[Google Cloud Repo](https://cloud.google.com/tools/repo/cloud-repositories/).
35+
More options (including browsing local source files) are coming soon.
36+
37+
This document only focuses on the Java debugger agent. Please see the
38+
this [page](https://cloud.google.com/tools/cloud-debugger/debugging) for
39+
explanation how to debug an application with the Cloud Debugger.
40+
41+
## Options for Getting Help
42+
43+
1. StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-debugger
44+
2. Google Group: cdbg-feedback@google.com
45+
46+
## Installation
47+
48+
The easiest way to install the debugger agent is to download the pre-built
49+
package from the Internet:
50+
51+
```shell
52+
mkdir /opt/cdbg
53+
wget -qO- https://storage.googleapis.com/cloud-debugger/compute-java/debian-wheezy/cdbg_java_agent_gce.tar.gz | tar xvz -C /opt/cdbg
54+
```
55+
56+
Alternatively you can build the debugger agent from source code:
57+
58+
```shell
59+
git clone https://github.com/GoogleCloudPlatform/cloud-debug-java.git
60+
cd cloud-debug-java
61+
chmod +x build.sh
62+
./build.sh
63+
```
64+
65+
## Setup
66+
67+
The Java Cloud Debugger agent is a
68+
[JVMTI](http://docs.oracle.com/javase/7/docs/technotes/guides/jvmti/)
69+
agent that needs to be enabled when JVM starts. The agent is enabled by
70+
using `-agentpath`
71+
[option](http://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html#starting)
72+
with Java launcher. Most debugger options are configured through system
73+
properties.
74+
75+
For example:
76+
77+
<pre>
78+
java <b>-agentpath:/opt/cdbg/cdbg_java_agent.so</b> -jar ~/myapp.jar
79+
</pre>
80+
81+
By default the debugger agent assumes that it runs on Google Compute Engine and
82+
uses local [metadata service](https://cloud.google.com/compute/docs/metadata) to
83+
obtain the credentials. You can still use the Java Cloud Debugger outside of
84+
Google Compute Engine or on a virtual machine that does not allow API access to
85+
all Google Cloud services. This would require setting up a
86+
[service account](#Service_Account).
87+
88+
### Web Servers
89+
90+
Java web servers usually start through a bootstrap process, and each web server
91+
has its own way of customizing Java options.
92+
93+
#### Tomcat
94+
95+
Add this line to `/etc/default/tomcat7` or `/etc/default/tomcat8`:
96+
97+
```shell
98+
JAVA_OPTS="${JAVA_OPTS} -agentpath:/opt/cdbg/cdbg_java_agent.so"
99+
```
100+
101+
If you run Tomcat in a Docker container, add this line to `Dockerfile` instead:
102+
103+
```
104+
ENV JAVA_OPTS -agentpath:/opt/cdbg/cdbg_java_agent.so
105+
```
106+
107+
#### Jetty
108+
109+
Add `cdbg.ini` file to `/var/lib/jetty/start.d`:
110+
111+
```ini
112+
--exec
113+
-agentpath:/opt/cdbg/cdbg_java_agent.so
114+
```
115+
116+
### Versioning
117+
118+
Developers normally run multiple versions of the application at the same time
119+
and multiple applications within the same project. You can tag each such entity
120+
with Cloud Debugger. This will make it easier to select the right application in
121+
the Cloud Debugger UI.
122+
123+
To tag the version of the application, please add one or more of these optional
124+
system properties:
125+
126+
<pre>
127+
-Dcom.google.cdbg.version=<i>myversion</i>
128+
-Dcom.google.cdbg.module=<i>mymodule</i>
129+
-Dcom.google.cdbg.minorversion=<i>minor version or build ID</i>
130+
</pre>
131+
132+
### Logging
133+
134+
By default the Java Cloud Debugger write its logs to `cdbg_java_agent.INFO` file
135+
in the default logging directory. It is possible to change the log directory
136+
as following:
137+
138+
```
139+
-agentpath:/opt/cdbg/cdbg_java_agent.so=--log_dir=/my/log/dir
140+
```
141+
142+
Alternatively you can make the Java Cloud Debugger log to *stderr*:
143+
144+
```
145+
-agentpath:/opt/cdbg/cdbg_java_agent.so=--logtostderr=1
146+
```
147+
148+
### Service Account
149+
150+
Service account authentication lets you run the debugger agent on any Linux
151+
machine, including outside of [Google Cloud Platform](https://cloud.google.com).
152+
The debugger agent authenticates against the backend with the service account
153+
created in [Google Developers Console](https://console.developers.google.com).
154+
If your application runs on Google Compute Engine,
155+
[metadata service authentication](#Setup) is an easier option.
156+
157+
The first step for this setup is to create the service account in .p12 format.
158+
Please see
159+
[OAuth](https://cloud.google.com/storage/docs/authentication?hl=en#generating-a-private-key)
160+
page for detailed instructions. If you don't have a Google Cloud Platform
161+
project, you can create one for free on
162+
[Google Developers Console](https://console.developers.google.com).
163+
164+
Once you have the service account, please note the service account e-mail,
165+
[project ID and project number](https://developers.google.com/console/help/new/#projectnumber).
166+
Then copy the .p12 file to all the machines that run your application.
167+
168+
You will need to install the debugger agent that supports the service account.
169+
The URL is: https://storage.googleapis.com/cloud-debugger/compute-java/debian-wheezy/cdbg_java_agent_service_account.tar.gz.
170+
11171

12-
For information on how to use the Cloud Debugger please see
13-
[Cloud Debugger](https://cloud.google.com/tools/cloud-debugger/).
172+
To enable the service account authentication add these arguments to Java
173+
launcher command (same way as with `-agentpath`):
14174

15-
Google provides a binary distribution for Java Cloud Debugger that you can
16-
use. This open package lets you build it from source code instead.
175+
<pre>
176+
-Dcom.google.cdbg.auth.serviceaccount.enable=<i>true</i>
177+
-Dcom.google.cdbg.auth.serviceaccount.projectid=<i>myprojectid</i>
178+
-Dcom.google.cdbg.auth.serviceaccount.projectnumber=<i>123456789</i>
179+
-Dcom.google.cdbg.auth.serviceaccount.email=<i>email@something.com</i>
180+
-Dcom.google.cdbg.auth.serviceaccount.p12file=<i>/opt/cdbg/svc.p12</i>
181+
</pre>

src/agent/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ HEADERS := \
140140
$(wildcard *.h) \
141141

142142
LIBS= \
143+
-ldl \
143144
-lrt \
144145
-pthread \
145146
$(THIRD_PARTY_LIB_PATH)/libjsoncpp.a \

src/agent/capture_data_collector.cc

Lines changed: 22 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
#include "method_locals.h"
2626
#include "model_util.h"
2727
#include "object_evaluator.h"
28-
#include "safe_method_caller.h"
2928
#include "value_formatter.h"
3029

3130
namespace devtools {
3231
namespace cdbg {
3332

34-
CaptureDataCollector::CaptureDataCollector() {
33+
CaptureDataCollector::CaptureDataCollector(JvmEvaluators* evaluators)
34+
: evaluators_(evaluators) {
3535
// Reserve "var_table_index" 0 for memory objects that we didn't capture
3636
// because collector ran out of quota.
3737
memory_objects_.push_back(MemoryObject());
@@ -44,21 +44,14 @@ CaptureDataCollector::~CaptureDataCollector() {
4444

4545

4646
void CaptureDataCollector::Collect(
47-
const Config& config,
48-
ClassFilesCache* class_files_cache,
49-
EvalCallStack* eval_call_stack,
50-
ClassIndexer* class_indexer,
51-
ClassMetadataReader* class_metadata_reader,
52-
MethodLocals* method_locals,
53-
ObjectEvaluator* object_evaluator,
5447
const std::vector<CompiledExpression>& watches,
5548
jthread thread) {
56-
DCHECK(eval_call_stack_ == nullptr) << "Collect can only be called once";
57-
eval_call_stack_ = CHECK_NOTNULL(eval_call_stack);
49+
std::unique_ptr<MethodCaller> pretty_printers_method_caller =
50+
evaluators_->method_caller_factory(Config::PRETTY_PRINTERS);
5851

5952
// Walk the call stack.
6053
std::vector<EvalCallStack::JvmFrame> jvm_frames;
61-
eval_call_stack_->Read(thread, &jvm_frames);
54+
evaluators_->eval_call_stack->Read(thread, &jvm_frames);
6255

6356
const int call_frames_count = jvm_frames.size();
6457
call_frames_.resize(call_frames_count);
@@ -68,22 +61,13 @@ void CaptureDataCollector::Collect(
6861
// Collect local variables.
6962
if ((depth < kMethodLocalsFrames) &&
7063
(jvm_frames[depth].code_location.method != nullptr)) {
71-
// We don't expect any methods to be called while reading fields.
72-
SafeMethodCaller method_caller(
73-
&config,
74-
Config::MethodCallQuota(), // Default quota of zero for everything.
75-
class_indexer,
76-
class_files_cache);
77-
7864
EvaluationContext evaluation_context;
7965
evaluation_context.thread = thread;
8066
evaluation_context.frame_depth = depth;
81-
evaluation_context.method_caller = &method_caller;
67+
evaluation_context.method_caller = pretty_printers_method_caller.get();
8268

8369
ReadLocalVariables(
8470
evaluation_context,
85-
class_metadata_reader,
86-
method_locals,
8771
jvm_frames[depth].code_location.method,
8872
jvm_frames[depth].code_location.location,
8973
&call_frames_[depth].arguments,
@@ -107,20 +91,16 @@ void CaptureDataCollector::Collect(
10791
watch_results_[i].compile_error_message = {ExpressionSensitiveData, { }};
10892

10993
} else if (watches[i].evaluator != nullptr) {
110-
SafeMethodCaller method_caller(
111-
&config,
112-
config.GetQuota(Config::EXPRESSION_EVALUATION),
113-
class_indexer,
114-
class_files_cache);
94+
std::unique_ptr<MethodCaller> expression_method_caller =
95+
evaluators_->method_caller_factory(Config::EXPRESSION_EVALUATION);
11596

11697
EvaluationContext evaluation_context;
11798
evaluation_context.thread = thread;
11899
evaluation_context.frame_depth = 0;
119-
evaluation_context.method_caller = &method_caller;
100+
evaluation_context.method_caller = expression_method_caller.get();
120101

121102
EvaluateWatchedExpression(
122103
evaluation_context,
123-
class_metadata_reader,
124104
*watches[i].evaluator,
125105
&watch_results_[i].evaluation_result);
126106

@@ -142,16 +122,10 @@ void CaptureDataCollector::Collect(
142122
++it_pending_object; // First entry has a special meaning of "buffer full".
143123
++captured_variable_table_size;
144124

145-
SafeMethodCaller method_caller(
146-
&config,
147-
config.GetQuota(Config::PRETTY_PRINTERS),
148-
class_indexer,
149-
class_files_cache);
150-
151125
while ((it_pending_object != memory_objects_.end()) &&
152126
CanCollectMoreMemoryObjects()) {
153-
object_evaluator->Evaluate(
154-
&method_caller,
127+
evaluators_->object_evaluator->Evaluate(
128+
pretty_printers_method_caller.get(),
155129
it_pending_object->object_ref,
156130
&it_pending_object->members);
157131

@@ -227,6 +201,11 @@ void CaptureDataCollector::Format(BreakpointModel* breakpoint) const {
227201
} else {
228202
object_variable.reset(new VariableModel);
229203

204+
object_variable->type = TypeNameFromSignature({
205+
JType::Object,
206+
GetObjectClassSignature(memory_object.object_ref)
207+
});
208+
230209
if (!memory_object.status.description.format.empty()) {
231210
object_variable->status =
232211
StatusMessageBuilder(memory_object.status).build();
@@ -245,8 +224,6 @@ void CaptureDataCollector::Format(BreakpointModel* breakpoint) const {
245224

246225
void CaptureDataCollector::ReadLocalVariables(
247226
const EvaluationContext& evaluation_context,
248-
ClassMetadataReader* class_metadata_reader,
249-
MethodLocals* method_locals,
250227
jmethodID method,
251228
jlocation location,
252229
std::vector<NamedJVariant>* arguments,
@@ -257,7 +234,7 @@ void CaptureDataCollector::ReadLocalVariables(
257234
}
258235

259236
std::shared_ptr<const MethodLocals::Entry> entry =
260-
method_locals->GetLocalVariables(method);
237+
evaluators_->method_locals->GetLocalVariables(method);
261238

262239
// TODO(vlif): refactor this function to add locals and arguments to
263240
// output vectors as we go.
@@ -310,7 +287,6 @@ void CaptureDataCollector::ReadLocalVariables(
310287

311288
void CaptureDataCollector::EvaluateWatchedExpression(
312289
const EvaluationContext& evaluation_context,
313-
ClassMetadataReader* class_metadata_reader,
314290
const ExpressionEvaluator& watch_evaluator,
315291
NamedJVariant* result) {
316292
ErrorOr<JVariant> evaluation_result =
@@ -373,8 +349,9 @@ std::unique_ptr<VariableModel> CaptureDataCollector::FormatVariable(
373349
ValueFormatter::Format(
374350
source,
375351
ValueFormatter::Options(),
376-
&formatted_value);
377-
target->value = formatted_value;
352+
&formatted_value,
353+
&target->type);
354+
target->value = std::move(formatted_value);
378355
} else {
379356
jobject ref = nullptr;
380357
const int* var_table_index = nullptr;
@@ -408,7 +385,7 @@ std::unique_ptr<VariableModel> CaptureDataCollector::FormatVariable(
408385
string CaptureDataCollector::GetFunctionName(int depth) const {
409386
const int frame_info_key = call_frames_[depth].frame_info_key;
410387
const auto& frame_info =
411-
eval_call_stack_->ResolveCallFrameKey(frame_info_key);
388+
evaluators_->eval_call_stack->ResolveCallFrameKey(frame_info_key);
412389

413390
string function_name =
414391
TypeNameFromJObjectSignature(frame_info.class_signature);
@@ -423,7 +400,7 @@ std::unique_ptr<SourceLocationModel>
423400
CaptureDataCollector::GetCallFrameSourceLocation(int depth) const {
424401
const int frame_info_key = call_frames_[depth].frame_info_key;
425402
const auto& frame_info =
426-
eval_call_stack_->ResolveCallFrameKey(frame_info_key);
403+
evaluators_->eval_call_stack->ResolveCallFrameKey(frame_info_key);
427404

428405
std::unique_ptr<SourceLocationModel> location(new SourceLocationModel);
429406

0 commit comments

Comments
 (0)