7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled source #
2 | ###################
3 | *.com
4 | *.class
5 | *.dll
6 | *.exe
7 | *.o
8 | *.so
9 |
10 | # Packages #
11 | ############
12 | # it's better to unpack these files and commit the raw source
13 | # git has its own built in compression methods
14 | *.7z
15 | *.dmg
16 | *.gz
17 | *.iso
18 | *.jar
19 | *.rar
20 | *.tar
21 | *.zip
22 |
23 | # Logs and databases #
24 | ######################
25 | *.log
26 |
27 | # OS generated files #
28 | ######################
29 | .DS_Store*
30 | ehthumbs.db
31 | Icon?
32 | Thumbs.db
33 |
34 | # Editor Files #
35 | ################
36 | *~
37 | *.swp
38 |
39 | # Gradle Files #
40 | ################
41 | .gradle
42 | .m2
43 |
44 | # Build output directies
45 | target/
46 | build/
47 |
48 | # IntelliJ specific files/directories
49 | out
50 | .idea
51 | *.ipr
52 | *.iws
53 | *.iml
54 | atlassian-ide-plugin.xml
55 |
56 | # Eclipse specific files/directories
57 | .classpath
58 | .project
59 | .settings
60 | .metadata
61 | bin/
62 |
63 | # NetBeans specific files/directories
64 | .nbattrs
65 |
66 |
--------------------------------------------------------------------------------
/src/main/webapp/css/simplegrid/1236_grid.css:
--------------------------------------------------------------------------------
1 | /* SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid)
2 | * http://simplegrid.info
3 | * by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com)
4 | * License: http://creativecommons.org/licenses/MIT/ */
5 |
6 | /* Containers */
7 | body { font-size: 1.125em; }
8 | .grid{ width:1206px; }
9 |
10 | /* 6-Col Grid Sizes */
11 | .slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:176px; } /* Sixths */
12 | .slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:382px; } /* Thirds */
13 | .slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:794px; } /* Two-Thirds */
14 | .slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:1000px; } /* Five-Sixths */
15 |
16 | /* 4-Col Grid Sizes */
17 | .slot-6,.slot-7,.slot-8,.slot-9{ width:279px; } /* Quarters */
18 | .slot-6-7-8,.slot-7-8-9{ width:897px; } /* Three-Quarters */
19 |
20 | /* 6-Col/4-Col Shared Grid Sizes */
21 | .slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:588px; } /* Halves */
--------------------------------------------------------------------------------
/src/main/webapp/css/simplegrid/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Crowd Favorite, Ltd.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/src/main/test/com/netflix/hystrix/dashboard/stream/UrlUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.netflix.hystrix.dashboard.stream;
2 |
3 | import org.junit.Test;
4 |
5 | /**
6 | * Copyright 2013 Netflix, Inc.
7 | *
8 | * Licensed under the Apache License, Version 2.0 (the "License");
9 | * you may not use this file except in compliance with the License.
10 | * You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing, software
15 | * distributed under the License is distributed on an "AS IS" BASIS,
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | * See the License for the specific language governing permissions and
18 | * limitations under the License.
19 | */
20 | /**
21 | * UrlUtilsTest unit tests
22 | *
23 | * @author diegopacheco
24 | *
25 | */
26 | public class UrlUtilsTest {
27 |
28 | @Test(expected=IllegalArgumentException.class)
29 | public void testReadXmlInputStreamWithNull() {
30 | UrlUtils.readXmlInputStream(null);
31 | }
32 |
33 | @Test(expected=IllegalArgumentException.class)
34 | public void testReadXmlInputStreamWithBlank() {
35 | UrlUtils.readXmlInputStream("");
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/webapp/css/simplegrid/986_grid.css:
--------------------------------------------------------------------------------
1 | /* SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid)
2 | * http://simplegrid.info
3 | * by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com)
4 | * License: http://creativecommons.org/licenses/MIT/ */
5 |
6 | /* Containers */
7 | body { font-size: 100%; }
8 | .grid{ width:966px; }
9 |
10 | /* Slots Setup */
11 | .slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5,.slot-0-1,.slot-0-1-2,.slot-0-1-2-3,.slot-0-1-2-3-4,.slot-0-1-2-3-4-5,.slot-1-2,.slot-1-2-3,.slot-1-2-3-4,.slot-1-2-3-4-5,.slot-2-3,.slot-2-3-4,.slot-2-3-4-5,.slot-3-4,.slot-3-4-5,.slot-4-5,.slot-6,.slot-7,.slot-8,.slot-9,.slot-6-7,.slot-6-7-8,.slot-6-7-8-9,.slot-7-8,.slot-7-8-9,.slot-8-9{ display:inline; float:left; margin-left:30px; }
12 |
13 | /* 6-Col Grid Sizes */
14 | .slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:136px; } /* Sixths */
15 | .slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:302px; } /* Thirds */
16 | .slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:634px; } /* Two-Thirds */
17 | .slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:800px; } /* Five-Sixths */
18 |
19 | /* 4-Col Grid Sizes */
20 | .slot-6,.slot-7,.slot-8,.slot-9{ width:219px; } /* Quarters */
21 | .slot-6-7-8,.slot-7-8-9{ width:717px; } /* Three-Quarters */
22 |
23 | /* 6-Col/4-Col Shared Grid Sizes */
24 | .slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:468px; } /* Halves */
--------------------------------------------------------------------------------
/src/main/webapp/js/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012, Michael Bostock
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * The name Michael Bostock may not be used to endorse or promote products
15 | derived from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
26 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/src/main/webapp/css/simplegrid/percentage_grid.css:
--------------------------------------------------------------------------------
1 | /* Extension of SimpleGrid by benjchristensen to allow percentage based sizing on very large displays
2 | *
3 | * SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid)
4 | * http://simplegrid.info
5 | * by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com)
6 | * License: http://creativecommons.org/licenses/MIT/ */
7 |
8 | /* Containers */
9 | body { font-size: 1.125em; }
10 | .grid{ width:100%; }
11 |
12 | /* Slots Setup */
13 | .slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5,.slot-0-1,.slot-0-1-2,.slot-0-1-2-3,.slot-0-1-2-3-4,.slot-0-1-2-3-4-5,.slot-1-2,.slot-1-2-3,.slot-1-2-3-4,.slot-1-2-3-4-5,.slot-2-3,.slot-2-3-4,.slot-2-3-4-5,.slot-3-4,.slot-3-4-5,.slot-4-5,.slot-6,.slot-7,.slot-8,.slot-9,.slot-6-7,.slot-6-7-8,.slot-6-7-8-9,.slot-7-8,.slot-7-8-9,.slot-8-9{ display:inline; float:left; margin-left:0px; }
14 |
15 |
16 | /* 6-Col Grid Sizes */
17 | .slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:16.6%; } /* Sixths */
18 | .slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:33.3%; } /* Thirds */
19 | .slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:66.6%; } /* Two-Thirds */
20 | .slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:83.3%; } /* Five-Sixths */
21 |
22 | /* 4-Col Grid Sizes */
23 | .slot-6,.slot-7,.slot-8,.slot-9{ width:25%; } /* Quarters */
24 | .slot-6-7-8,.slot-7-8-9{ width:75%; } /* Three-Quarters */
25 |
26 | /* 6-Col/4-Col Shared Grid Sizes */
27 | .slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:50%; } /* Halves */
--------------------------------------------------------------------------------
/src/main/webapp/components/hystrixThreadPool/templates/hystrixThreadPool.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hystrix Dashboard
2 |
3 | This is a dashboard for monitoring applications using Hystrix (https://github.com/Netflix/Hystrix).
4 |
5 | This project previously was a part of the [Netflix/Hystrix](https://github.com/Netflix/Hystrix) project. It is now deprecated and no longer supported. See the below security section for necessary security considerations.
6 |
7 | View the [Dashboard Wiki](https://github.com/Netflix-Skunkworks/hystrix-dashboard/wiki) for more information including installation instructions.
8 |
9 |
10 |
11 |
12 | # Security
13 |
14 | Hystrix dashboard is not intended to be deployed on untrusted networks, or without external authentication and authorization. Specifically, hystrix-dashboard does not offer any default security protection and can perform server side requests based on user provided urls.
15 |
16 | A security advisory exist for hystrix-dashboard at [nflx-2018-001](https://github.com/Netflix/security-bulletins/blob/master/advisories/nflx-2018-001.md)
17 |
18 |
19 | # Run via Gradle
20 |
21 | ```
22 | $ git clone https://github.com/Netflix/Hystrix.git
23 | $ cd Hystrix/hystrix-dashboard
24 | $ ./gradlew appRun
25 | > Building > :appRun > Running at http://localhost:7979/hystrix-dashboard
26 | ```
27 |
28 | Once running, open http://localhost:7979/hystrix-dashboard.
29 |
30 | # Run as standalone Java application
31 |
32 | @kennedyoliveira has written a standalone app, documented at : https://github.com/kennedyoliveira/standalone-hystrix-dashboard
33 |
34 | # Example
35 |
36 | Example screenshot from iPad while monitoring Netflix API:
37 |
38 |
157 | Cluster via Turbine (default cluster): http://turbine-hostname:port/turbine.stream
158 |
159 | Cluster via Turbine (custom cluster): http://turbine-hostname:port/turbine.stream?cluster=[clusterName]
160 |
161 | Single Hystrix App: http://hystrix-app:port/hystrix.stream
162 |
163 | Delay: ms
164 |
165 | Title:
166 | Authorization:
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
--------------------------------------------------------------------------------
/src/main/java/com/netflix/hystrix/dashboard/stream/ProxyStreamServlet.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015 Netflix, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.netflix.hystrix.dashboard.stream;
17 |
18 | import java.io.IOException;
19 | import java.io.InputStream;
20 | import java.io.OutputStream;
21 | import java.util.Map;
22 |
23 | import javax.servlet.ServletException;
24 | import javax.servlet.http.HttpServlet;
25 | import javax.servlet.http.HttpServletRequest;
26 | import javax.servlet.http.HttpServletResponse;
27 |
28 | import org.apache.http.Header;
29 | import org.apache.http.HttpHeaders;
30 | import org.apache.http.HttpResponse;
31 | import org.apache.http.HttpStatus;
32 | import org.apache.http.client.HttpClient;
33 | import org.apache.http.client.methods.HttpGet;
34 | import org.apache.http.impl.client.DefaultHttpClient;
35 | import org.apache.http.impl.conn.PoolingClientConnectionManager;
36 | import org.apache.http.params.HttpConnectionParams;
37 | import org.apache.http.params.HttpParams;
38 | import org.slf4j.Logger;
39 | import org.slf4j.LoggerFactory;
40 |
41 | /**
42 | * Proxy an EventStream request (data.stream via proxy.stream) since EventStream does not yet support CORS (https://bugs.webkit.org/show_bug.cgi?id=61862)
43 | * so that a UI can request a stream from a different server.
44 | */
45 | public class ProxyStreamServlet extends HttpServlet {
46 | private static final long serialVersionUID = 1L;
47 | private static final Logger logger = LoggerFactory.getLogger(ProxyStreamServlet.class);
48 |
49 | public ProxyStreamServlet() {
50 | super();
51 | }
52 |
53 | /**
54 | * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
55 | */
56 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
57 | String origin = request.getParameter("origin");
58 | String authorization = request.getParameter("authorization");
59 | if (origin == null) {
60 | response.setStatus(500);
61 | response.getWriter().println("Required parameter 'origin' missing. Example: 107.20.175.135:7001");
62 | return;
63 | }
64 | origin = origin.trim();
65 |
66 | HttpGet httpget = null;
67 | InputStream is = null;
68 | boolean hasFirstParameter = false;
69 | StringBuilder url = new StringBuilder();
70 | if (!origin.startsWith("http")) {
71 | url.append("http://");
72 | }
73 | url.append(origin);
74 | if (origin.contains("?")) {
75 | hasFirstParameter = true;
76 | }
77 | @SuppressWarnings("unchecked")
78 | Map params = request.getParameterMap();
79 | for (String key : params.keySet()) {
80 | if (!key.equals("origin") && !key.equals("authorization")) {
81 | String[] values = params.get(key);
82 | String value = values[0].trim();
83 | if (hasFirstParameter) {
84 | url.append("&");
85 | } else {
86 | url.append("?");
87 | hasFirstParameter = true;
88 | }
89 | url.append(key).append("=").append(value);
90 | }
91 | }
92 | String proxyUrl = url.toString();
93 | logger.info("\n\nProxy opening connection to: {}\n\n", proxyUrl);
94 | try {
95 | httpget = new HttpGet(proxyUrl);
96 | if (authorization != null) {
97 | httpget.addHeader("Authorization", authorization);
98 | }
99 | HttpClient client = ProxyConnectionManager.httpClient;
100 | HttpResponse httpResponse = client.execute(httpget);
101 | int statusCode = httpResponse.getStatusLine().getStatusCode();
102 | if (statusCode == HttpStatus.SC_OK) {
103 | // writeTo swallows exceptions and never quits even if outputstream is throwing IOExceptions (such as broken pipe) ... since the inputstream is infinite
104 | // httpResponse.getEntity().writeTo(new OutputStreamWrapper(response.getOutputStream()));
105 | // so I copy it manually ...
106 | is = httpResponse.getEntity().getContent();
107 |
108 | // set headers
109 | for (Header header : httpResponse.getAllHeaders()) {
110 | if (!HttpHeaders.TRANSFER_ENCODING.equals(header.getName())) {
111 | response.addHeader(header.getName(), header.getValue());
112 | }
113 | }
114 |
115 | // copy data from source to response
116 | OutputStream os = response.getOutputStream();
117 | int b = -1;
118 | while ((b = is.read()) != -1) {
119 | try {
120 | os.write(b);
121 | if (b == 10 /** flush buffer on line feed */) {
122 | os.flush();
123 | }
124 | } catch (Exception e) {
125 | if (e.getClass().getSimpleName().equalsIgnoreCase("ClientAbortException")) {
126 | // don't throw an exception as this means the user closed the connection
127 | logger.debug("Connection closed by client. Will stop proxying ...");
128 | // break out of the while loop
129 | break;
130 | } else {
131 | // received unknown error while writing so throw an exception
132 | throw new RuntimeException(e);
133 | }
134 | }
135 | }
136 | }
137 | } catch (Exception e) {
138 | logger.error("Error proxying request: " + url, e);
139 | } finally {
140 | if (httpget != null) {
141 | try {
142 | httpget.abort();
143 | } catch (Exception e) {
144 | logger.error("failed aborting proxy connection.", e);
145 | }
146 | }
147 |
148 | // httpget.abort() MUST be called first otherwise is.close() hangs (because data is still streaming?)
149 | if (is != null) {
150 | // this should already be closed by httpget.abort() above
151 | try {
152 | is.close();
153 | } catch (Exception e) {
154 | // e.printStackTrace();
155 | }
156 | }
157 | }
158 | }
159 |
160 | private static class ProxyConnectionManager {
161 | private final static PoolingClientConnectionManager threadSafeConnectionManager = new PoolingClientConnectionManager();
162 | private final static HttpClient httpClient = new DefaultHttpClient(threadSafeConnectionManager);
163 |
164 | static {
165 | logger.debug("Initialize ProxyConnectionManager");
166 | /* common settings */
167 | HttpParams httpParams = httpClient.getParams();
168 | HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
169 | HttpConnectionParams.setSoTimeout(httpParams, 10000);
170 |
171 | /* number of connections to allow */
172 | threadSafeConnectionManager.setDefaultMaxPerRoute(400);
173 | threadSafeConnectionManager.setMaxTotal(400);
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/main/webapp/monitor/monitor.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hystrix Monitor
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 |
187 |
188 |
189 |
--------------------------------------------------------------------------------
/LICENSE-2.0.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2012 Netflix, Inc.
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/src/main/webapp/components/hystrixThreadPool/hystrixThreadPool.js:
--------------------------------------------------------------------------------
1 |
2 | (function(window) {
3 |
4 | // cache the templates we use on this page as global variables (asynchronously)
5 | jQuery.get(getRelativePath("../components/hystrixThreadPool/templates/hystrixThreadPool.html"), function(data) {
6 | htmlTemplate = data;
7 | });
8 | jQuery.get(getRelativePath("../components/hystrixThreadPool/templates/hystrixThreadPoolContainer.html"), function(data) {
9 | htmlTemplateContainer = data;
10 | });
11 |
12 | function getRelativePath(path) {
13 | var p = location.pathname.slice(0, location.pathname.lastIndexOf("/")+1);
14 | return p + path;
15 | }
16 |
17 | /**
18 | * Object containing functions for displaying and updating the UI with streaming data.
19 | *
20 | * Publish this externally as "HystrixThreadPoolMonitor"
21 | */
22 | window.HystrixThreadPoolMonitor = function(index, containerId) {
23 |
24 | var self = this; // keep scope under control
25 |
26 | this.index = index;
27 | this.containerId = containerId;
28 |
29 | /**
30 | * Initialization on construction
31 | */
32 | // intialize various variables we use for visualization
33 | var maxXaxisForCircle="40%";
34 | var maxYaxisForCircle="40%";
35 | var maxRadiusForCircle="125";
36 | var maxDomain = 2000;
37 |
38 | self.circleRadius = d3.scale.pow().exponent(0.5).domain([0, maxDomain]).range(["5", maxRadiusForCircle]); // requests per second per host
39 | self.circleYaxis = d3.scale.linear().domain([0, maxDomain]).range(["30%", maxXaxisForCircle]);
40 | self.circleXaxis = d3.scale.linear().domain([0, maxDomain]).range(["30%", maxYaxisForCircle]);
41 | self.colorRange = d3.scale.linear().domain([10, 25, 40, 50]).range(["green", "#FFCC00", "#FF9900", "red"]);
42 | self.errorPercentageColorRange = d3.scale.linear().domain([0, 10, 35, 50]).range(["grey", "black", "#FF9900", "red"]);
43 |
44 | /**
45 | * We want to keep sorting in the background since data values are always changing, so this will re-sort every X milliseconds
46 | * to maintain whatever sort the user (or default) has chosen.
47 | *
48 | * In other words, sorting only for adds/deletes is not sufficient as all but alphabetical sort are dynamically changing.
49 | */
50 | setInterval(function() {
51 | // sort since we have added a new one
52 | self.sortSameAsLast();
53 | }, 1000)
54 |
55 | /**
56 | * END of Initialization on construction
57 | */
58 |
59 | /**
60 | * Event listener to handle new messages from EventSource as streamed from the server.
61 | */
62 | /* public */ self.eventSourceMessageListener = function(e) {
63 | var data = JSON.parse(e.data);
64 | if(data) {
65 | data.index = self.index;
66 | // check for reportingHosts (if not there, set it to 1 for singleHost vs cluster)
67 | if(!data.reportingHosts) {
68 | data.reportingHosts = 1;
69 | }
70 |
71 | if(data && data.type == 'HystrixThreadPool') {
72 | if (data.deleteData == 'true') {
73 | deleteThreadPool(data.escapedName);
74 | } else {
75 | displayThreadPool(data);
76 | }
77 | }
78 | }
79 | }
80 |
81 | /**
82 | * Pre process the data before displying in the UI.
83 | * e.g Get Averages from sums, do rate calculation etc.
84 | */
85 | function preProcessData(data) {
86 | validateData(data);
87 | // escape string used in jQuery & d3 selectors
88 | data.escapedName = data.name.replace(/([ !"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g,'\\$1') + '_' + data.index;
89 | // do math
90 | converAllAvg(data);
91 | calcRatePerSecond(data);
92 | }
93 |
94 | function converAllAvg(data) {
95 | convertAvg(data, "propertyValue_queueSizeRejectionThreshold", false);
96 |
97 | // the following will break when it becomes a compound string if the property is dynamically changed
98 | convertAvg(data, "propertyValue_metricsRollingStatisticalWindowInMilliseconds", false);
99 | }
100 |
101 | function convertAvg(data, key, decimal) {
102 | if (decimal) {
103 | data[key] = roundNumber(data[key]/data["reportingHosts"]);
104 | } else {
105 | data[key] = Math.floor(data[key]/data["reportingHosts"]);
106 | }
107 | }
108 |
109 | function calcRatePerSecond(data) {
110 | var numberSeconds = data["propertyValue_metricsRollingStatisticalWindowInMilliseconds"] / 1000;
111 |
112 | var totalThreadsExecuted = data["rollingCountThreadsExecuted"];
113 | if (totalThreadsExecuted < 0) {
114 | totalThreadsExecuted = 0;
115 | }
116 | data["ratePerSecond"] = roundNumber(totalThreadsExecuted / numberSeconds);
117 | data["ratePerSecondPerHost"] = roundNumber(totalThreadsExecuted / numberSeconds / data["reportingHosts"]);
118 | }
119 |
120 | function validateData(data) {
121 |
122 | assertNotNull(data,"type");
123 | assertNotNull(data,"name");
124 | // assertNotNull(data,"currentTime");
125 | assertNotNull(data,"currentActiveCount");
126 | assertNotNull(data,"currentCompletedTaskCount");
127 | assertNotNull(data,"currentCorePoolSize");
128 | assertNotNull(data,"currentLargestPoolSize");
129 | assertNotNull(data,"currentMaximumPoolSize");
130 | assertNotNull(data,"currentPoolSize");
131 | assertNotNull(data,"currentQueueSize");
132 | assertNotNull(data,"currentTaskCount");
133 | assertNotNull(data,"rollingCountThreadsExecuted");
134 | assertNotNull(data,"rollingMaxActiveThreads");
135 | assertNotNull(data,"reportingHosts");
136 |
137 | assertNotNull(data,"propertyValue_queueSizeRejectionThreshold");
138 | assertNotNull(data,"propertyValue_metricsRollingStatisticalWindowInMilliseconds");
139 | }
140 |
141 | function assertNotNull(data, key) {
142 | if(data[key] == undefined) {
143 | if (key == "dependencyOwner") {
144 | data["dependencyOwner"] = data.name;
145 | } else {
146 | throw new Error("Key Missing: " + key + " for " + data.name)
147 | }
148 | }
149 | }
150 |
151 | /**
152 | * Method to display the THREAD_POOL data
153 | *
154 | * @param data
155 | */
156 | /* private */ function displayThreadPool(data) {
157 |
158 | try {
159 | preProcessData(data);
160 | } catch (err) {
161 | log("Failed preProcessData: " + err.message);
162 | return;
163 | }
164 |
165 | // add the 'addCommas' function to the 'data' object so the HTML templates can use it
166 | data.addCommas = addCommas;
167 | // add the 'roundNumber' function to the 'data' object so the HTML templates can use it
168 | data.roundNumber = roundNumber;
169 |
170 | var addNew = false;
171 | // check if we need to create the container
172 | if(!$('#THREAD_POOL_' + data.escapedName).length) {
173 | // it doesn't exist so add it
174 | var html = tmpl(htmlTemplateContainer, data);
175 | // remove the loading thing first
176 | $('#' + containerId + ' span.loading').remove();
177 | // get the current last column and remove the 'last' class from it
178 | $('#' + containerId + ' div.last').removeClass('last');
179 | // now create the new data and add it
180 | $('#' + containerId + '').append(html);
181 | // add the 'last' class to the column we just added
182 | $('#' + containerId + ' div.monitor').last().addClass('last');
183 |
184 | // add the default sparkline graph
185 | d3.selectAll('#graph_THREAD_POOL_' + data.escapedName + ' svg').append("svg:path");
186 |
187 | // remember this is new so we can trigger a sort after setting data
188 | addNew = true;
189 | }
190 |
191 | // set the rate on the div element so it's available for sorting
192 | $('#THREAD_POOL_' + data.escapedName).attr('rate_value', data.ratePerSecondPerHost);
193 |
194 | // now update/insert the data
195 | $('#THREAD_POOL_' + data.escapedName + ' div.monitor_data').html(tmpl(htmlTemplate, data));
196 |
197 | // set variables for circle visualization
198 | var rate = data.ratePerSecondPerHost;
199 | // we will treat each item in queue as 1% of an error visualization
200 | // ie. 5 threads in queue per instance == 5% error percentage
201 | var errorPercentage = data.currentQueueSize / data.reportingHosts;
202 |
203 | updateCircle('#THREAD_POOL_' + data.escapedName + ' circle', rate, errorPercentage);
204 |
205 | if(addNew) {
206 | // sort since we added a new circuit
207 | self.sortSameAsLast();
208 | }
209 | }
210 |
211 | /* round a number to X digits: num => the number to round, dec => the number of decimals */
212 | /* private */ function roundNumber(num) {
213 | var dec=1; // we are hardcoding to support only 1 decimal so that our padding logic at the end is simple
214 | var result = Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
215 | var resultAsString = result.toString();
216 | if(resultAsString.indexOf('.') == -1) {
217 | resultAsString = resultAsString + '.';
218 | for(var i=0; i parseInt(maxXaxisForCircle)) {
229 | newXaxisForCircle = maxXaxisForCircle;
230 | }
231 | var newYaxisForCircle = self.circleYaxis(rate);
232 | if(parseInt(newYaxisForCircle) > parseInt(maxYaxisForCircle)) {
233 | newYaxisForCircle = maxYaxisForCircle;
234 | }
235 | var newRadiusForCircle = self.circleRadius(rate);
236 | if(parseInt(newRadiusForCircle) > parseInt(maxRadiusForCircle)) {
237 | newRadiusForCircle = maxRadiusForCircle;
238 | }
239 |
240 | d3.selectAll(cssTarget)
241 | .transition()
242 | .duration(400)
243 | .attr("cy", newYaxisForCircle)
244 | .attr("cx", newXaxisForCircle)
245 | .attr("r", newRadiusForCircle)
246 | .style("fill", self.colorRange(errorPercentage));
247 | }
248 |
249 | /* private */ function deleteThreadPool(poolName) {
250 | $('#THREAD_POOL_' + poolName).remove();
251 | }
252 |
253 | }
254 |
255 | // public methods for sorting
256 | HystrixThreadPoolMonitor.prototype.sortByVolume = function() {
257 | var direction = "desc";
258 | if(this.sortedBy == 'rate_desc') {
259 | direction = 'asc';
260 | }
261 | this.sortByVolumeInDirection(direction);
262 | }
263 |
264 | HystrixThreadPoolMonitor.prototype.sortByVolumeInDirection = function(direction) {
265 | this.sortedBy = 'rate_' + direction;
266 | $('#' + this.containerId + ' div.monitor').tsort({order: direction, attr: 'rate_value'});
267 | }
268 |
269 | HystrixThreadPoolMonitor.prototype.sortAlphabetically = function() {
270 | var direction = "asc";
271 | if(this.sortedBy == 'alph_asc') {
272 | direction = 'desc';
273 | }
274 | this.sortAlphabeticalInDirection(direction);
275 | }
276 |
277 | HystrixThreadPoolMonitor.prototype.sortAlphabeticalInDirection = function(direction) {
278 | this.sortedBy = 'alph_' + direction;
279 | $('#' + this.containerId + ' div.monitor').tsort("p.name", {order: direction});
280 | }
281 |
282 | HystrixThreadPoolMonitor.prototype.sortByMetricInDirection = function(direction, metric) {
283 | $('#' + this.containerId + ' div.monitor').tsort(metric, {order: direction});
284 | }
285 |
286 | // this method is for when new divs are added to cause the elements to be sorted to whatever the user last chose
287 | HystrixThreadPoolMonitor.prototype.sortSameAsLast = function() {
288 | if(this.sortedBy == 'alph_asc') {
289 | this.sortAlphabeticalInDirection('asc');
290 | } else if(this.sortedBy == 'alph_desc') {
291 | this.sortAlphabeticalInDirection('desc');
292 | } else if(this.sortedBy == 'rate_asc') {
293 | this.sortByVolumeInDirection('asc');
294 | } else if(this.sortedBy == 'rate_desc') {
295 | this.sortByVolumeInDirection('desc');
296 | } else if(this.sortedBy == 'error_asc') {
297 | this.sortByErrorInDirection('asc');
298 | } else if(this.sortedBy == 'error_desc') {
299 | this.sortByErrorInDirection('desc');
300 | } else if(this.sortedBy == 'lat90_asc') {
301 | this.sortByMetricInDirection('asc', 'p90');
302 | } else if(this.sortedBy == 'lat90_desc') {
303 | this.sortByMetricInDirection('desc', 'p90');
304 | } else if(this.sortedBy == 'lat99_asc') {
305 | this.sortByMetricInDirection('asc', 'p99');
306 | } else if(this.sortedBy == 'lat99_desc') {
307 | this.sortByMetricInDirection('desc', 'p99');
308 | } else if(this.sortedBy == 'lat995_asc') {
309 | this.sortByMetricInDirection('asc', 'p995');
310 | } else if(this.sortedBy == 'lat995_desc') {
311 | this.sortByMetricInDirection('desc', 'p995');
312 | } else if(this.sortedBy == 'latMean_asc') {
313 | this.sortByMetricInDirection('asc', 'pMean');
314 | } else if(this.sortedBy == 'latMean_desc') {
315 | this.sortByMetricInDirection('desc', 'pMean');
316 | } else if(this.sortedBy == 'latMedian_asc') {
317 | this.sortByMetricInDirection('asc', 'pMedian');
318 | } else if(this.sortedBy == 'latMedian_desc') {
319 | this.sortByMetricInDirection('desc', 'pMedian');
320 | }
321 | }
322 |
323 | // default sort type and direction
324 | this.sortedBy = 'alph_asc';
325 |
326 |
327 | // a temporary home for the logger until we become more sophisticated
328 | function log(message) {
329 | console.log(message);
330 | };
331 |
332 | function addCommas(nStr){
333 | nStr += '';
334 | if(nStr.length <=3) {
335 | return nStr; //shortcut if we don't need commas
336 | }
337 | x = nStr.split('.');
338 | x1 = x[0];
339 | x2 = x.length > 1 ? '.' + x[1] : '';
340 | var rgx = /(\d+)(\d{3})/;
341 | while (rgx.test(x1)) {
342 | x1 = x1.replace(rgx, '$1' + ',' + '$2');
343 | }
344 | return x1 + x2;
345 | }
346 | })(window)
347 |
348 |
349 |
--------------------------------------------------------------------------------
/src/main/webapp/components/hystrixCommand/hystrixCommand.js:
--------------------------------------------------------------------------------
1 |
2 | (function(window) {
3 |
4 | // cache the templates we use on this page as global variables (asynchronously)
5 | jQuery.get(getRelativePath("../components/hystrixCommand/templates/hystrixCircuit.html"), function(data) {
6 | hystrixTemplateCircuit = data;
7 | });
8 | jQuery.get(getRelativePath("../components/hystrixCommand/templates/hystrixCircuitContainer.html"), function(data) {
9 | hystrixTemplateCircuitContainer = data;
10 | });
11 |
12 | function getRelativePath(path) {
13 | var p = location.pathname.slice(0, location.pathname.lastIndexOf("/")+1);
14 | return p + path;
15 | }
16 |
17 | /**
18 | * Object containing functions for displaying and updating the UI with streaming data.
19 | *
20 | * Publish this externally as "HystrixCommandMonitor"
21 | */
22 | window.HystrixCommandMonitor = function(index, containerId, args) {
23 |
24 | var self = this; // keep scope under control
25 | self.args = args;
26 | if(self.args == undefined) {
27 | self.args = {};
28 | }
29 |
30 | this.index = index;
31 | this.containerId = containerId;
32 |
33 | /**
34 | * Initialization on construction
35 | */
36 | // intialize various variables we use for visualization
37 | var maxXaxisForCircle="40%";
38 | var maxYaxisForCircle="40%";
39 | var maxRadiusForCircle="125";
40 |
41 | // CIRCUIT_BREAKER circle visualization settings
42 | self.circuitCircleRadius = d3.scale.pow().exponent(0.5).domain([0, 400]).range(["5", maxRadiusForCircle]); // requests per second per host
43 | self.circuitCircleYaxis = d3.scale.linear().domain([0, 400]).range(["30%", maxXaxisForCircle]);
44 | self.circuitCircleXaxis = d3.scale.linear().domain([0, 400]).range(["30%", maxYaxisForCircle]);
45 | self.circuitColorRange = d3.scale.linear().domain([10, 25, 40, 50]).range(["green", "#FFCC00", "#FF9900", "red"]);
46 | self.circuitErrorPercentageColorRange = d3.scale.linear().domain([0, 10, 35, 50]).range(["grey", "black", "#FF9900", "red"]);
47 |
48 | /**
49 | * We want to keep sorting in the background since data values are always changing, so this will re-sort every X milliseconds
50 | * to maintain whatever sort the user (or default) has chosen.
51 | *
52 | * In other words, sorting only for adds/deletes is not sufficient as all but alphabetical sort are dynamically changing.
53 | */
54 | setInterval(function() {
55 | // sort since we have added a new one
56 | self.sortSameAsLast();
57 | }, 10000);
58 |
59 |
60 | /**
61 | * END of Initialization on construction
62 | */
63 |
64 | /**
65 | * Event listener to handle new messages from EventSource as streamed from the server.
66 | */
67 | /* public */ self.eventSourceMessageListener = function(e) {
68 | var data = JSON.parse(e.data);
69 | if(data) {
70 | data.index = self.index;
71 | // check for reportingHosts (if not there, set it to 1 for singleHost vs cluster)
72 | if(!data.reportingHosts) {
73 | data.reportingHosts = 1;
74 | }
75 |
76 | if(data && data.type == 'HystrixCommand') {
77 | if (data.deleteData == 'true') {
78 | deleteCircuit(data.escapedName);
79 | } else {
80 | displayCircuit(data);
81 | }
82 | }
83 | }
84 | };
85 |
86 | /**
87 | * Pre process the data before displying in the UI.
88 | * e.g Get Averages from sums, do rate calculation etc.
89 | */
90 | function preProcessData(data) {
91 | // set defaults for values that may be missing from older streams
92 | setIfMissing(data, "rollingCountBadRequests", 0);
93 | // assert all the values we need
94 | validateData(data);
95 | // escape string used in jQuery & d3 selectors
96 | data.escapedName = data.name.replace(/([ !"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g,'\\$1') + '_' + data.index;
97 | // do math
98 | convertAllAvg(data);
99 | calcRatePerSecond(data);
100 | }
101 |
102 | function setIfMissing(data, key, defaultValue) {
103 | if(data[key] == undefined) {
104 | data[key] = defaultValue;
105 | }
106 | }
107 |
108 | /**
109 | * Since the stream of data can be aggregated from multiple hosts in a tiered manner
110 | * the aggregation just sums everything together and provides us the denominator (reportingHosts)
111 | * so we must divide by it to get an average per instance value.
112 | *
113 | * We want to do this on any numerical values where we want per instance rather than cluster-wide sum.
114 | */
115 | function convertAllAvg(data) {
116 | convertAvg(data, "errorPercentage", true);
117 | convertAvg(data, "latencyExecute_mean", false);
118 | }
119 |
120 | function convertAvg(data, key, decimal) {
121 | if (decimal) {
122 | data[key] = getInstanceAverage(data[key], data["reportingHosts"], decimal);
123 | } else {
124 | data[key] = getInstanceAverage(data[key], data["reportingHosts"], decimal);
125 | }
126 | }
127 |
128 | function getInstanceAverage(value, reportingHosts, decimal) {
129 | if (decimal) {
130 | return roundNumber(value/reportingHosts);
131 | } else {
132 | return Math.floor(value/reportingHosts);
133 | }
134 | }
135 |
136 | function calcRatePerSecond(data) {
137 | var numberSeconds = data["propertyValue_metricsRollingStatisticalWindowInMilliseconds"] / 1000;
138 |
139 | var totalRequests = data["requestCount"];
140 | if (totalRequests < 0) {
141 | totalRequests = 0;
142 | }
143 | data["ratePerSecond"] = roundNumber(totalRequests / numberSeconds);
144 | data["ratePerSecondPerHost"] = roundNumber(totalRequests / numberSeconds / data["reportingHosts"]) ;
145 | }
146 |
147 | function validateData(data) {
148 | assertNotNull(data,"reportingHosts");
149 | assertNotNull(data,"type");
150 | assertNotNull(data,"name");
151 | assertNotNull(data,"group");
152 | // assertNotNull(data,"currentTime");
153 | assertNotNull(data,"isCircuitBreakerOpen");
154 | assertNotNull(data,"errorPercentage");
155 | assertNotNull(data,"errorCount");
156 | assertNotNull(data,"requestCount");
157 | assertNotNull(data,"rollingCountCollapsedRequests");
158 | assertNotNull(data,"rollingCountExceptionsThrown");
159 | assertNotNull(data,"rollingCountFailure");
160 | assertNotNull(data,"rollingCountFallbackFailure");
161 | assertNotNull(data,"rollingCountFallbackRejection");
162 | assertNotNull(data,"rollingCountFallbackSuccess");
163 | assertNotNull(data,"rollingCountResponsesFromCache");
164 | assertNotNull(data,"rollingCountSemaphoreRejected");
165 | assertNotNull(data,"rollingCountShortCircuited");
166 | assertNotNull(data,"rollingCountSuccess");
167 | assertNotNull(data,"rollingCountThreadPoolRejected");
168 | assertNotNull(data,"rollingCountTimeout");
169 | assertNotNull(data,"rollingCountBadRequests");
170 | assertNotNull(data,"currentConcurrentExecutionCount");
171 | assertNotNull(data,"latencyExecute_mean");
172 | assertNotNull(data,"latencyExecute");
173 | assertNotNull(data,"propertyValue_circuitBreakerRequestVolumeThreshold");
174 | assertNotNull(data,"propertyValue_circuitBreakerSleepWindowInMilliseconds");
175 | assertNotNull(data,"propertyValue_circuitBreakerErrorThresholdPercentage");
176 | assertNotNull(data,"propertyValue_circuitBreakerForceOpen");
177 | assertNotNull(data,"propertyValue_circuitBreakerForceClosed");
178 | assertNotNull(data,"propertyValue_executionIsolationStrategy");
179 | assertNotNull(data,"propertyValue_executionIsolationThreadTimeoutInMilliseconds");
180 | assertNotNull(data,"propertyValue_executionIsolationThreadInterruptOnTimeout");
181 | // assertNotNull(data,"propertyValue_executionIsolationThreadPoolKeyOverride");
182 | assertNotNull(data,"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests");
183 | assertNotNull(data,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests");
184 | assertNotNull(data,"propertyValue_requestCacheEnabled");
185 | assertNotNull(data,"propertyValue_requestLogEnabled");
186 | assertNotNull(data,"propertyValue_metricsRollingStatisticalWindowInMilliseconds");
187 | }
188 |
189 | function assertNotNull(data, key) {
190 | if(data[key] == undefined) {
191 | throw new Error("Key Missing: " + key + " for " + data.name);
192 | }
193 | }
194 |
195 | /**
196 | * Method to display the CIRCUIT data
197 | *
198 | * @param data
199 | */
200 | /* private */ function displayCircuit(data) {
201 |
202 | try {
203 | preProcessData(data);
204 | } catch (err) {
205 | log("Failed preProcessData: " + err.message);
206 | return;
207 | }
208 |
209 | // add the 'addCommas' function to the 'data' object so the HTML templates can use it
210 | data.addCommas = addCommas;
211 | // add the 'roundNumber' function to the 'data' object so the HTML templates can use it
212 | data.roundNumber = roundNumber;
213 | // add the 'getInstanceAverage' function to the 'data' object so the HTML templates can use it
214 | data.getInstanceAverage = getInstanceAverage;
215 |
216 | var addNew = false;
217 | // check if we need to create the container
218 | if(!$('#CIRCUIT_' + data.escapedName).length) {
219 | // args for display
220 | if(self.args.includeDetailIcon != undefined && self.args.includeDetailIcon) {
221 | data.includeDetailIcon = true;
222 | }else {
223 | data.includeDetailIcon = false;
224 | }
225 |
226 | // it doesn't exist so add it
227 | var html = tmpl(hystrixTemplateCircuitContainer, data);
228 | // remove the loading thing first
229 | $('#' + containerId + ' span.loading').remove();
230 | // now create the new data and add it
231 | $('#' + containerId + '').append(html);
232 |
233 | // add the default sparkline graph
234 | d3.selectAll('#graph_CIRCUIT_' + data.escapedName + ' svg').append("svg:path");
235 |
236 | // remember this is new so we can trigger a sort after setting data
237 | addNew = true;
238 | }
239 |
240 |
241 | // now update/insert the data
242 | $('#CIRCUIT_' + data.escapedName + ' div.monitor_data').html(tmpl(hystrixTemplateCircuit, data));
243 |
244 | var ratePerSecond = data.ratePerSecond;
245 | var ratePerSecondPerHost = data.ratePerSecondPerHost;
246 | var ratePerSecondPerHostDisplay = ratePerSecondPerHost;
247 | var errorThenVolume = isNaN( ratePerSecond )? -1: (data.errorPercentage * 100000000) + ratePerSecond;
248 | // set the rates on the div element so it's available for sorting
249 | $('#CIRCUIT_' + data.escapedName).attr('rate_value', ratePerSecond);
250 | $('#CIRCUIT_' + data.escapedName).attr('error_then_volume', errorThenVolume);
251 |
252 | // update errorPercentage color on page
253 | $('#CIRCUIT_' + data.escapedName + ' a.errorPercentage').css('color', self.circuitErrorPercentageColorRange(data.errorPercentage));
254 |
255 | updateCircle('circuit', '#CIRCUIT_' + data.escapedName + ' circle', ratePerSecondPerHostDisplay, data.errorPercentage);
256 |
257 | if(data.graphValues) {
258 | // we have a set of values to initialize with
259 | updateSparkline('circuit', '#CIRCUIT_' + data.escapedName + ' path', data.graphValues);
260 | } else {
261 | updateSparkline('circuit', '#CIRCUIT_' + data.escapedName + ' path', ratePerSecond);
262 | }
263 |
264 | if(addNew) {
265 | // sort since we added a new circuit
266 | self.sortSameAsLast();
267 | }
268 | }
269 |
270 | /* round a number to X digits: num => the number to round, dec => the number of decimals */
271 | /* private */ function roundNumber(num) {
272 | var dec=1;
273 | var result = Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
274 | var resultAsString = result.toString();
275 | if(resultAsString.indexOf('.') == -1) {
276 | resultAsString = resultAsString + '.0';
277 | }
278 | return resultAsString;
279 | };
280 |
281 |
282 |
283 |
284 | /* private */ function updateCircle(variablePrefix, cssTarget, rate, errorPercentage) {
285 | var newXaxisForCircle = self[variablePrefix + 'CircleXaxis'](rate);
286 | if(parseInt(newXaxisForCircle) > parseInt(maxXaxisForCircle)) {
287 | newXaxisForCircle = maxXaxisForCircle;
288 | }
289 | var newYaxisForCircle = self[variablePrefix + 'CircleYaxis'](rate);
290 | if(parseInt(newYaxisForCircle) > parseInt(maxYaxisForCircle)) {
291 | newYaxisForCircle = maxYaxisForCircle;
292 | }
293 | var newRadiusForCircle = self[variablePrefix + 'CircleRadius'](rate);
294 | if(parseInt(newRadiusForCircle) > parseInt(maxRadiusForCircle)) {
295 | newRadiusForCircle = maxRadiusForCircle;
296 | }
297 |
298 | d3.selectAll(cssTarget)
299 | .transition()
300 | .duration(400)
301 | .attr("cy", newYaxisForCircle)
302 | .attr("cx", newXaxisForCircle)
303 | .attr("r", newRadiusForCircle)
304 | .style("fill", self[variablePrefix + 'ColorRange'](errorPercentage));
305 | }
306 |
307 | /* private */ function updateSparkline(variablePrefix, cssTarget, newDataPoint) {
308 | var currentTimeMilliseconds = new Date().getTime();
309 | var data = self[variablePrefix + cssTarget + '_data'];
310 | if(typeof data == 'undefined') {
311 | // else it's new
312 | if(typeof newDataPoint == 'object') {
313 | // we received an array of values, so initialize with it
314 | data = newDataPoint;
315 | } else {
316 | // v: VALUE, t: TIME_IN_MILLISECONDS
317 | data = [{"v":parseFloat(newDataPoint),"t":currentTimeMilliseconds}];
318 | }
319 | self[variablePrefix + cssTarget + '_data'] = data;
320 | } else {
321 | if(typeof newDataPoint == 'object') {
322 | /* if an array is passed in we'll replace the cached one */
323 | data = newDataPoint;
324 | } else {
325 | // else we just add to the existing one
326 | data.push({"v":parseFloat(newDataPoint),"t":currentTimeMilliseconds});
327 | }
328 | }
329 |
330 | while(data.length > 200) { // 400 should be plenty for the 2 minutes we have the scale set to below even with a very low update latency
331 | // remove data so we don't keep increasing forever
332 | data.shift();
333 | }
334 |
335 | if(data.length == 1 && data[0].v == 0) {
336 | //console.log("we have a single 0 so skipping");
337 | // don't show if we have a single 0
338 | return;
339 | }
340 |
341 | if(data.length > 1 && data[0].v == 0 && data[1].v != 0) {
342 | //console.log("we have a leading 0 so removing it");
343 | // get rid of a leading 0 if the following number is not a 0
344 | data.shift();
345 | }
346 |
347 | var xScale = d3.time.scale().domain([new Date(currentTimeMilliseconds-(60*1000*2)), new Date(currentTimeMilliseconds)]).range([0, 140]);
348 |
349 | var yMin = d3.min(data, function(d) { return d.v; });
350 | var yMax = d3.max(data, function(d) { return d.v; });
351 | var yScale = d3.scale.linear().domain([yMin, yMax]).nice().range([60, 0]); // y goes DOWN, so 60 is the "lowest"
352 |
353 | sparkline = d3.svg.line()
354 | // assign the X function to plot our line as we wish
355 | .x(function(d,i) {
356 | // return the X coordinate where we want to plot this datapoint based on the time
357 | return xScale(new Date(d.t));
358 | })
359 | .y(function(d) {
360 | return yScale(d.v);
361 | })
362 | .interpolate("basis");
363 |
364 | d3.selectAll(cssTarget).attr("d", sparkline(data));
365 | }
366 |
367 | /* private */ function deleteCircuit(circuitName) {
368 | $('#CIRCUIT_' + circuitName).remove();
369 | }
370 |
371 | };
372 |
373 | // public methods for sorting
374 | HystrixCommandMonitor.prototype.sortByVolume = function() {
375 | var direction = "desc";
376 | if(this.sortedBy == 'rate_desc') {
377 | direction = 'asc';
378 | }
379 | this.sortByVolumeInDirection(direction);
380 | };
381 |
382 | HystrixCommandMonitor.prototype.sortByVolumeInDirection = function(direction) {
383 | this.sortedBy = 'rate_' + direction;
384 | $('#' + this.containerId + ' div.monitor').tsort({order: direction, attr: 'rate_value'});
385 | };
386 |
387 | HystrixCommandMonitor.prototype.sortAlphabetically = function() {
388 | var direction = "asc";
389 | if(this.sortedBy == 'alph_asc') {
390 | direction = 'desc';
391 | }
392 | this.sortAlphabeticalInDirection(direction);
393 | };
394 |
395 | HystrixCommandMonitor.prototype.sortAlphabeticalInDirection = function(direction) {
396 | this.sortedBy = 'alph_' + direction;
397 | $('#' + this.containerId + ' div.monitor').tsort("p.name", {order: direction});
398 | };
399 |
400 |
401 | HystrixCommandMonitor.prototype.sortByError = function() {
402 | var direction = "desc";
403 | if(this.sortedBy == 'error_desc') {
404 | direction = 'asc';
405 | }
406 | this.sortByErrorInDirection(direction);
407 | };
408 |
409 | HystrixCommandMonitor.prototype.sortByErrorInDirection = function(direction) {
410 | this.sortedBy = 'error_' + direction;
411 | $('#' + this.containerId + ' div.monitor').tsort(".errorPercentage .value", {order: direction});
412 | };
413 |
414 | HystrixCommandMonitor.prototype.sortByErrorThenVolume = function() {
415 | var direction = "desc";
416 | if(this.sortedBy == 'error_then_volume_desc') {
417 | direction = 'asc';
418 | }
419 | this.sortByErrorThenVolumeInDirection(direction);
420 | };
421 |
422 | HystrixCommandMonitor.prototype.sortByErrorThenVolumeInDirection = function(direction) {
423 | this.sortedBy = 'error_then_volume_' + direction;
424 | $('#' + this.containerId + ' div.monitor').tsort({order: direction, attr: 'error_then_volume'});
425 | };
426 |
427 | HystrixCommandMonitor.prototype.sortByLatency90 = function() {
428 | var direction = "desc";
429 | if(this.sortedBy == 'lat90_desc') {
430 | direction = 'asc';
431 | }
432 | this.sortedBy = 'lat90_' + direction;
433 | this.sortByMetricInDirection(direction, ".latency90 .value");
434 | };
435 |
436 | HystrixCommandMonitor.prototype.sortByLatency99 = function() {
437 | var direction = "desc";
438 | if(this.sortedBy == 'lat99_desc') {
439 | direction = 'asc';
440 | }
441 | this.sortedBy = 'lat99_' + direction;
442 | this.sortByMetricInDirection(direction, ".latency99 .value");
443 | };
444 |
445 | HystrixCommandMonitor.prototype.sortByLatency995 = function() {
446 | var direction = "desc";
447 | if(this.sortedBy == 'lat995_desc') {
448 | direction = 'asc';
449 | }
450 | this.sortedBy = 'lat995_' + direction;
451 | this.sortByMetricInDirection(direction, ".latency995 .value");
452 | };
453 |
454 | HystrixCommandMonitor.prototype.sortByLatencyMean = function() {
455 | var direction = "desc";
456 | if(this.sortedBy == 'latMean_desc') {
457 | direction = 'asc';
458 | }
459 | this.sortedBy = 'latMean_' + direction;
460 | this.sortByMetricInDirection(direction, ".latencyMean .value");
461 | };
462 |
463 | HystrixCommandMonitor.prototype.sortByLatencyMedian = function() {
464 | var direction = "desc";
465 | if(this.sortedBy == 'latMedian_desc') {
466 | direction = 'asc';
467 | }
468 | this.sortedBy = 'latMedian_' + direction;
469 | this.sortByMetricInDirection(direction, ".latencyMedian .value");
470 | };
471 |
472 | HystrixCommandMonitor.prototype.sortByMetricInDirection = function(direction, metric) {
473 | $('#' + this.containerId + ' div.monitor').tsort(metric, {order: direction});
474 | };
475 |
476 | // this method is for when new divs are added to cause the elements to be sorted to whatever the user last chose
477 | HystrixCommandMonitor.prototype.sortSameAsLast = function() {
478 | if(this.sortedBy == 'alph_asc') {
479 | this.sortAlphabeticalInDirection('asc');
480 | } else if(this.sortedBy == 'alph_desc') {
481 | this.sortAlphabeticalInDirection('desc');
482 | } else if(this.sortedBy == 'rate_asc') {
483 | this.sortByVolumeInDirection('asc');
484 | } else if(this.sortedBy == 'rate_desc') {
485 | this.sortByVolumeInDirection('desc');
486 | } else if(this.sortedBy == 'error_asc') {
487 | this.sortByErrorInDirection('asc');
488 | } else if(this.sortedBy == 'error_desc') {
489 | this.sortByErrorInDirection('desc');
490 | } else if(this.sortedBy == 'error_then_volume_asc') {
491 | this.sortByErrorThenVolumeInDirection('asc');
492 | } else if(this.sortedBy == 'error_then_volume_desc') {
493 | this.sortByErrorThenVolumeInDirection('desc');
494 | } else if(this.sortedBy == 'lat90_asc') {
495 | this.sortByMetricInDirection('asc', '.latency90 .value');
496 | } else if(this.sortedBy == 'lat90_desc') {
497 | this.sortByMetricInDirection('desc', '.latency90 .value');
498 | } else if(this.sortedBy == 'lat99_asc') {
499 | this.sortByMetricInDirection('asc', '.latency99 .value');
500 | } else if(this.sortedBy == 'lat99_desc') {
501 | this.sortByMetricInDirection('desc', '.latency99 .value');
502 | } else if(this.sortedBy == 'lat995_asc') {
503 | this.sortByMetricInDirection('asc', '.latency995 .value');
504 | } else if(this.sortedBy == 'lat995_desc') {
505 | this.sortByMetricInDirection('desc', '.latency995 .value');
506 | } else if(this.sortedBy == 'latMean_asc') {
507 | this.sortByMetricInDirection('asc', '.latencyMean .value');
508 | } else if(this.sortedBy == 'latMean_desc') {
509 | this.sortByMetricInDirection('desc', '.latencyMean .value');
510 | } else if(this.sortedBy == 'latMedian_asc') {
511 | this.sortByMetricInDirection('asc', '.latencyMedian .value');
512 | } else if(this.sortedBy == 'latMedian_desc') {
513 | this.sortByMetricInDirection('desc', '.latencyMedian .value');
514 | }
515 | };
516 |
517 | // default sort type and direction
518 | this.sortedBy = 'alph_asc';
519 |
520 |
521 | // a temporary home for the logger until we become more sophisticated
522 | function log(message) {
523 | console.log(message);
524 | };
525 |
526 | function addCommas(nStr){
527 | nStr += '';
528 | if(nStr.length <=3) {
529 | return nStr; //shortcut if we don't need commas
530 | }
531 | x = nStr.split('.');
532 | x1 = x[0];
533 | x2 = x.length > 1 ? '.' + x[1] : '';
534 | var rgx = /(\d+)(\d{3})/;
535 | while (rgx.test(x1)) {
536 | x1 = x1.replace(rgx, '$1' + ',' + '$2');
537 | }
538 | return x1 + x2;
539 | }
540 | })(window);
541 |
542 |
543 |
--------------------------------------------------------------------------------
/src/main/webapp/js/jquery.min.js:
--------------------------------------------------------------------------------
1 | /*! jQuery v1.7.2 jquery.com | jquery.org/license */
2 | (function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
a",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="