What is XRebel? What you can use it for? How to use it with Liferay? Let’s find out!
What is XRebel
First a few words of introduction on what is XRebel and what can we use it for.
XRebel, as it’s tagline says, is a “lightweight java profiler”.
What can it do for you? It can plug into your Java application and will give you insights on how and what your application is doing in the following areas:
- database queries – especially handy for Hibernate users, as it gives you the ability to get the select statement AND the actual parameters that were bound
- calling remote services – during integration with third party systems via some sort of web services (be it SOAP or REST) you need to provide specific requests and responses your system made during an erroneous call so that the other system provider can verify the correctness of it. XRebel gives easy access to the communication chain (with some limitations)
- web requests – XRebel will show you what requests are being sent to your app and the responses the system produced
- web session – see what you store in the session and check if the session object is not exploding in memory due to too much data
XRebel + Liferay – connecting the dots
Some initial assumptions regarding your setup:
- XRebel 2.0.0 (Implementation-Version: 201504071124) installed at %XREBEL_PATH%
- Java: jdk1.6.0_37 + jre1.6.0_37
- Liferay 6.0.6 with bundled Tomcat 6.0.29 (Liferay Portal Community Edition 6.0.6 CE (Bunyan / Build 6006 / February 17, 2011) download here) installed at %LIFERAY_HOME% with default settings (HSQL Database, port 8080)
To start working with XRebel we need to add one line to JAVA_OPTS variable (%LIFERAY_HOME%tomcat-6.0.29binsetenv.bat/%LIFERAY_HOME%tomcat-6.0.29binsetenv.sh):
“-javaagent:”%XREBEL_PATH%xrebel.jar”
After starting Liferay we have a message in tomcat console log from XRebel:
2015-05-02 14:22:03 XRebel: Started XRebel for application: http://localhost:8080/
2015-05-02 14:22:03 XRebel: XRebel UI is available at http://localhost:8080/xrebel
If we run our application on http://localhost:8080/ it will now include XRebel GUI. It may not work for all requests, especially with RESTful applications. To make it work we need to open it in a new browser tab http://localhost:8080/xrebel, where you will find a standalone UI for XRebel. There we can see all the requests targeted at application deployed in the ROOT context.
After we login into Liferay (user: test@liferay.com) we can see all HTTP requests with timings and percentage statistics for methods called the on server side.
In my opinion the most valuable thing in XRebel is the Query tab. In this example we can see SQL query with filled parameters and this query we can just copy and paste to our favourite SQL IDE and execute it or check execution plan.
We can see all objects stored in session just like on the screen:
Real-life example – integration of Liferay with National Bank of Poland currency service
To start testing we create xrebel-test portlet to see how XRebel can help us in developing with Liferay.
We create one servlet which returns a JSON-formatted currencies list. We are using our library to download currencies from NBP (National Bank of Poland). We want to download currencies and log some event data to the database.
Our servlet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
static Gson gson = new Gson();
static SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd”);
static {
GsonBuilder gsonBuilder = new GsonBuilder();
gson = gsonBuilder.create();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
CurrencyDownloader currencyDownloader = new CurrencyDownloader();
try {
AverageCurrencyTable averageCurrencyTable = currencyDownloader.getCurrencyTableByDate(CurrencyTableType.AVERAGE, sdf.parse(“2015-05-05”));
CurrencyDownloadLog currencyDownloadLog = CurrencyDownloadLogLocalServiceUtil.createCurrencyDownloadLog(CounterLocalServiceUtil.increment(CurrencyDownloadLog.class.getName()));
currencyDownloadLog.setDownloadDate(Calendar.getInstance().getTime());
currencyDownloadLog.setRecordCount(averageCurrencyTable.getRecords().size());
CurrencyDownloadLogLocalServiceUtil.updateCurrencyDownloadLog(currencyDownloadLog);
resp.getWriter().append(gson.toJson(averageCurrencyTable));
resp.getWriter().flush();
resp.getWriter().close();
} catch (Exception e) {
e.printStackTrace();
}
}
|
Our servlet “/xrebel-test/currenciesDownload” returns:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
|
{
‘records’:[
{
‘averageRate’:0.1084,
‘currencyName’:‘bat (Tajlandia)’,
‘currencyCode’:‘THB’,
‘conversionRate’:1
},
{
‘averageRate’:3.6205,
‘currencyName’:‘dolar ameryka?ski’,
‘currencyCode’:‘USD’,
‘conversionRate’:1
},
{
‘averageRate’:2.8508,
‘currencyName’:‘dolar australijski’,
‘currencyCode’:‘AUD’,
‘conversionRate’:1
},
{
‘averageRate’:0.467,
‘currencyName’:‘dolar Hongkongu’,
‘currencyCode’:‘HKD’,
‘conversionRate’:1
},
{
‘averageRate’:2.9883,
‘currencyName’:‘dolar kanadyjski’,
‘currencyCode’:‘CAD’,
‘conversionRate’:1
},
{
‘averageRate’:2.7189,
‘currencyName’:‘dolar nowozelandzki’,
‘currencyCode’:‘NZD’,
‘conversionRate’:1
},
{
‘averageRate’:2.7079,
‘currencyName’:‘dolar singapurski’,
‘currencyCode’:‘SGD’,
‘conversionRate’:1
},
{
‘averageRate’:4.0179,
‘currencyName’:‘euro’,
‘currencyCode’:‘EUR’,
‘conversionRate’:1
},
{
‘averageRate’:1.3325,
‘currencyName’:‘forint (W?gry)’,
‘currencyCode’:‘HUF’,
‘conversionRate’:100
},
{
‘averageRate’:3.8619,
‘currencyName’:‘frank szwajcarski’,
‘currencyCode’:‘CHF’,
‘conversionRate’:1
},
{
‘averageRate’:5.4758,
‘currencyName’:‘funt szterling’,
‘currencyCode’:‘GBP’,
‘conversionRate’:1
},
{
‘averageRate’:0.1712,
‘currencyName’:‘hrywna (Ukraina)’,
‘currencyCode’:‘UAH’,
‘conversionRate’:1
},
{
‘averageRate’:3.01,
‘currencyName’:‘jen (Japonia)’,
‘currencyCode’:‘JPY’,
‘conversionRate’:100
},
{
‘averageRate’:0.147,
‘currencyName’:‘korona czeska’,
‘currencyCode’:‘CZK’,
‘conversionRate’:1
},
{
‘averageRate’:0.5383,
‘currencyName’:‘korona du?ska’,
‘currencyCode’:‘DKK’,
‘conversionRate’:1
},
{
‘averageRate’:2.7296,
‘currencyName’:‘korona islandzka’,
‘currencyCode’:‘ISK’,
‘conversionRate’:100
},
{
‘averageRate’:0.4728,
‘currencyName’:‘korona norweska’,
‘currencyCode’:‘NOK’,
‘conversionRate’:1
},
{
‘averageRate’:0.4314,
‘currencyName’:‘korona szwedzka’,
‘currencyCode’:‘SEK’,
‘conversionRate’:1
},
{
‘averageRate’:0.5295,
‘currencyName’:‘kuna (Chorwacja)’,
‘currencyCode’:‘HRK’,
‘conversionRate’:1
},
{
‘averageRate’:0.9081,
‘currencyName’:‘lej rumu?ski’,
‘currencyCode’:‘RON’,
‘conversionRate’:1
},
{
‘averageRate’:2.0544,
‘currencyName’:‘lew (Bu?garia)’,
‘currencyCode’:‘BGN’,
‘conversionRate’:1
},
{
‘averageRate’:1.3361,
‘currencyName’:‘lira turecka’,
‘currencyCode’:‘TRY’,
‘conversionRate’:1
},
{
‘averageRate’:0.9333,
‘currencyName’:‘nowy izraelski szekel’,
‘currencyCode’:‘ILS’,
‘conversionRate’:1
},
{
‘averageRate’:0.589,
‘currencyName’:‘peso chilijskie’,
‘currencyCode’:‘CLP’,
‘conversionRate’:100
},
{
‘averageRate’:0.0811,
‘currencyName’:‘peso filipi?skie’,
‘currencyCode’:‘PHP’,
‘conversionRate’:1
},
{
‘averageRate’:0.2343,
‘currencyName’:‘peso meksyka?skie’,
‘currencyCode’:‘MXN’,
‘conversionRate’:1
},
{
‘averageRate’:0.3007,
‘currencyName’:‘rand (Republika Po?udniowej Afryki)’,
‘currencyCode’:‘ZAR’,
‘conversionRate’:1
},
{
‘averageRate’:1.173,
‘currencyName’:‘real (Brazylia)’,
‘currencyCode’:‘BRL’,
‘conversionRate’:1
},
{
‘averageRate’:1.0017,
‘currencyName’:‘ringgit (Malezja)’,
‘currencyCode’:‘MYR’,
‘conversionRate’:1
},
{
‘averageRate’:0.0705,
‘currencyName’:‘rubel rosyjski’,
‘currencyCode’:‘RUB’,
‘conversionRate’:1
},
{
‘averageRate’:2.7719,
‘currencyName’:‘rupia indonezyjska’,
‘currencyCode’:‘IDR’,
‘conversionRate’:10000
},
{
‘averageRate’:5.704,
‘currencyName’:‘rupia indyjska’,
‘currencyCode’:‘INR’,
‘conversionRate’:100
},
{
‘averageRate’:0.3341,
‘currencyName’:‘won po?udniowokorea?ski’,
‘currencyCode’:‘KRW’,
‘conversionRate’:100
},
{
‘averageRate’:0.5847,
‘currencyName’:‘yuan renminbi (Chiny)’,
‘currencyCode’:‘CNY’,
‘conversionRate’:1
},
{
‘averageRate’:5.0434,
‘currencyName’:‘SDR (MFW)’,
‘currencyCode’:‘XDR’,
‘conversionRate’:1
}
],
‘tableNumber’:‘085/A/NBP/2015″,
‘tableId‘:’15a085″,
‘publicationDate’:‘May 5,
2015 12:00:00 AM’
}
|
In XRebel UI(in context of our portlet) we can see:
As we can see XRebel detected our call to NBP and displayed Request/Response. However there is a little problem because we couldn’t see the whole response body from NBP service. I’ve tried to set the property „xrebel.injection.log_response” to true with hopes the it will help – to no avail. I think it would be great if it was possible to see the whole response body, but as of now it’s not possible – there’s a hard limit of 2048 characters. The reason for this is that the content might be too big to render all at once. In future releases there is supposed to be some solution for this problem.
The most interesting feature for me is SQL query preview. We can see our select query for Counter and CurrencyDownloadLog entities, but we can’t see full data update and insert queries – the statements are there but the parameters bound to specific call are missing.
If we set the property „xrebel.injection.log_response” to true we can see the insert and update queries in xrebel.log file, but again, without bound parameters:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
2015–05–07 08:12:22.998 INFO [Module Loader] [90/http–8080–4] Initializing dependent modules in sun.reflect.DelegatingClassLoader@11183d8
2015–05–07 08:12:22.999 WARN [SQL] [102/pool–1–thread–1] Parsing ‘update Counter set currentId=? where name=?’ did not succeed: class com.zeroturnaround.xrebel.bc not an enum
2015–05–07 08:12:22.999 WARN [SQL] [102/pool–1–thread–1] update Counter set currentId=? where name=?
2015–05–07 08:12:22.999 DEBUG [IO] [90/http–8080–4] Registered sql query under Request #40: update Counter set currentId=? where name=?
2015–05–07 08:12:23.005 WARN [SQL] [102/pool–1–thread–1] Parsing ‘select currencydo0_.id as id314_0_, currencydo0_.download_date as download2_314_0_, currencydo0_.record_count as record3_314_0_ from CURRENCY_DOWNL…’ did not succeed: class com.zeroturnaround.xrebel.bc not an enum
2015–05–07 08:12:23.005 WARN [SQL] [102/pool–1–thread–1] select currencydo0_.id as id314_0_, currencydo0_.download_date as download2_314_0_, currencydo0_.record_count as record3_314_0_ from CURRENCY_DOWNLOAD_LOG currencydo0_ where currencydo0_.id=?
2015–05–07 08:12:23.005 DEBUG [IO] [90/http–8080–4] Registered sql query under Request #40: select currencydo0_.id as id314_0_, currencydo0_.download_date as download2_314_0_, currencydo0_.record_count as record3_314_0_ from CURRENCY_DOWNLOAD_LOG currencydo0_ where currencydo0_.id=?
2015–05–07 08:12:23.007 DEBUG [IO] [90/http–8080–4] Registered Hibernate stack info under Request #40: Session.flush
2015–05–07 08:12:23.008 WARN [SQL] [102/pool–1–thread–1] Parsing ‘insert into CURRENCY_DOWNLOAD_LOG (download_date, record_count, id) values (?, ?, ?)’ did not succeed: class com.zeroturnaround.xrebel.bc not an enum
2015–05–07 08:12:23.008 WARN [SQL] [102/pool–1–thread–1] insert into CURRENCY_DOWNLOAD_LOG (download_date, record_count, id) values (?, ?, ?)
2015–05–07 08:12:23.008 DEBUG [IO] [90/http–8080–4] Registered sql query under Request #40: insert into CURRENCY_DOWNLOAD_LOG (download_date, record_count, id) values (?, ?, ?)
|
We’ve sent above issue to XRebel team and they added a fix. I think it will be soon published in new release.
In Windows 7 xrebel.log file is located at %USERPROFILE%.xrebel and it contains logs from all our applications and overrides in next start of application. If we need, we can change this location just adding a VM argument:
-Dxrebel.log.file=/new/path/to/xrebel.log.
What is more, we can add next argument to set debug mode:
-Dxrebel.log=trace
Summary
To sum it up, the setup of XRebel with Liferay was pretty easy and in short amount of time I was able to get insights on the stuff my app was doing. XRebel provides a clear and useful UI to get the data needed for application profiling. As of writing of this post there are a few features that I would love to have to in XRebel to make the tool more complete but as of now it’s already very handy and can help with lots of common profiling tasks.