Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fast rendering Panning and Zooming SVG based callgraph #75

Open
wants to merge 76 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
41ebb39
[TASK] Code cleanup
Nov 21, 2013
0c30f74
[FEATURE] Add Normalize.css
Nov 23, 2013
520e7a3
[BUGFIX] Do not display link if this is not enabled in the config
Nov 23, 2013
a0078c7
[FEATURE] Improve typo3.css
Nov 23, 2013
f071583
[FEATURE] Restore invert button
Nov 23, 2013
e9402fe
[FEATURE] Generating callgraphs as gifs succeeds more often than png
Nov 23, 2013
751e095
[BUGFIX] Restore labels on pie chart, write μs instead of microsec
Nov 23, 2013
dcbd75a
[FEATURE] Add configuration option to load custom style sheet
Nov 24, 2013
dfbf09e
[TASK] Cleanup chart.phtml
Nov 24, 2013
4c3452a
[TASK] Lightened up the invert image
Nov 25, 2013
c909a1f
[FEATURE] Update to jQuery 2.0.3
Nov 25, 2013
8656dfd
[FEATURE] Update tablesorter and stickytableheaders jQuery plugins
Nov 25, 2013
6713f7c
[FEATURE] Update highcharts
Nov 25, 2013
2be5ab1
[TASK] Code cleanup
Nov 25, 2013
5960a04
[BUGFIX] Fix form triggers. They did not work because the submit butt…
Nov 26, 2013
eff72dc
[FEATURE] Zooming and Panning SVG based call graph
Nov 27, 2013
58d47ab
[BUGFIX] Set proper focal point for dblclick zoom
Nov 27, 2013
086caa9
[FEATURE] Clickable call graph
Nov 27, 2013
88c8fcd
[TASK] Code cleanup
Nov 27, 2013
b3309e2
[FEATURE] Show pointer cursor for call graph links
Nov 27, 2013
4aaeeeb
[FEATURE] Proper number formatting based on configured locale, thousa…
Dec 13, 2013
4d0271f
[CLEANUP] Remove debugging statement
Dec 13, 2013
01fc6d0
[BUGFIX] Align units when in a span tag
Dec 13, 2013
04d3a41
[FEATURE] Cleanup and extend code for charting of similar urls.
Dec 14, 2013
e7d6b1a
[FEATURE] Cleanup table rendering (reduce row height)
Dec 16, 2013
020d73d
[FEATURE] Show ellipsis on text-overflow in td
Dec 16, 2013
c09c945
[BUGFIX] Fix tooltip, reverse default chart order (most recent on the…
Dec 16, 2013
cdccb87
[FEATURE] CallGraph: Layout node data in table format
Dec 19, 2013
cc5ae7a
[FEATURE] CallGraph: Add loading indicator
Dec 19, 2013
d1a4ae0
[BUGFIX] Diff view. Allow for negative pmu and excl_pmu values
Dec 20, 2013
e837db5
[BUGFIX] Only attach tablesorter to tables tagged with class tablesor…
Dec 20, 2013
da4fa3b
[TASK] Reduce table bloat; classitis
Dec 20, 2013
bd36dce
[TASK] Reduce table bloat; remove spans around units
Dec 21, 2013
813e27d
[FEATURE] Support negative values in printSeconds() and printBytes()
Dec 21, 2013
944b4cc
[FEATURE] Fix number formatting in diff view
Dec 21, 2013
a551683
[TASK] Remove breaks from column headers
Dec 21, 2013
5247389
[TASK] Remove str_replace("<br* calls
Dec 21, 2013
b5b9ade
[TASK] Reduce table bloat
Dec 27, 2013
4c27367
[BUGFIX] "Show only Critical Path" now actually works correctly
Dec 27, 2013
c8b1081
[FEATURE] Default callgraph display to 'critical path only' by default
Dec 29, 2013
b84f6d0
[FEATURE] Add parent -> child tooltip to edges and labels so you can …
Dec 29, 2013
f52a4ca
[FEATURE] Added title attributes with the unformatted values to the r…
Dec 29, 2013
ae98d2a
[TASK] Render a '-' instead of 'n/a' for a formatted zero bytesize
Dec 29, 2013
66cd644
[FEATURE] Added graphs for 'most expensive calls by CPU Time' and 'mo…
Dec 30, 2013
08fee45
[BUGFIX] Disabled sticky table headers. It kills scrolling performanc…
Dec 30, 2013
869fb6d
[FEATURE] Link data labels in pie charts directly to the drill down c…
Dec 30, 2013
4eff022
[TASK] Unlink the 'Other' and 'Loading' segments in the pie charts
Dec 30, 2013
908379b
[TASK] Cleanup pie charts data labels
Dec 31, 2013
93216c0
[BUGFIX] Fix the 'Other' data labels for the pie charts
Dec 31, 2013
3bd19c0
[BUGFIX] Pie charts: Allow backslashes in function names
Jan 1, 2014
4c00ee5
[FEATURE] Pie charts: format the cal count
Jan 1, 2014
fb3ab5b
[FEATURE] Number formatting of the call count in the callgraph
Jan 2, 2014
d41cf1d
[BUGFIX] Increase height of dash-header so Firefox renders the URL ov…
Jan 2, 2014
d14c2a7
[TASK] Reduce table bloat
Jan 3, 2014
54850a3
[TASK] Number formatting for the Y axis of the url comaprison chart
Jan 4, 2014
14ee16f
[BUGFIX] Fix boundary conditions for seconds formatters
Jan 4, 2014
8301263
[BUGFIX] Fix off by 1000 error in second formatter for pie chart
Jan 4, 2014
77fb0f5
[BUGFIX] Make typo3.css the default stylesheet
Jan 4, 2014
03f9ffa
[BUGFIX] Fix point links in URL comparison chart
Jan 4, 2014
e38e2e6
[BUGFIX] Fix off by 1000 error in URL comparison chart tooltip
Jan 4, 2014
a572596
[FEATURE] Add dataTables JavaScript DOM sorting
Jan 18, 2014
cb4e767
[BUGFIX] Only render multiline label when dot version supports it
Feb 24, 2014
b96f3d7
Allow configuration of unix sockets instead of tcp for pdo
mrubinsk Mar 2, 2014
871b761
Avoid undefined index errors.
mrubinsk Mar 5, 2014
9ceeda2
Avoid undefined index when not tracking memory data.
mrubinsk Mar 5, 2014
8baad0d
Merge pull request #1 from mrubinsk/undefined_index
Mar 5, 2014
1cf4ded
Merge pull request #3 from mrubinsk/undefined_mem
Mar 5, 2014
86b5b81
Merge pull request #2 from mrubinsk/sockets
Mar 5, 2014
61eb499
[FEATURE] Log total method count to dedicated database field
Mar 7, 2014
ece7a26
Add Mysqli sql database schema
Feb 4, 2016
dbf1839
Add support for tideways profiler
Mar 5, 2016
3902c01
Remove debug code
Mar 5, 2016
f7ad8ec
Update README.markdown
hom3chuk May 28, 2017
4d8e577
Update README.HIGHCHARTS
hom3chuk May 28, 2017
b674305
Update INSTALL
hom3chuk May 28, 2017
d8553ad
Merge pull request #4 from hom3chuk/typo
Tuurlijk May 28, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion INSTALL
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
3. Copy xhprof_lib/config.sample.php to xhprof_lib/config.php
4. Edit xhprof_lib/config.php with your database type and credentials. Set control IPs, and other fun stuff
Control IPs allow you to specify which IPs will be shown the footer linking directly to the results, and also which IPs will be able to turn profiling on and off
You can set this value to FALSE, which disables that protection all togethor.
You can set this value to FALSE, which disables that protection altogether.
5. Create a table as required for your database, SQL is available inside utils/Db/<database of choice>.php. If you're prefixing runs IDs, note that it will affect the length requried for the ID column.
5. Load up index.php, ensure happiness
7. Edit the virtual host configuration for the site you'd like to profile to
Expand Down
2 changes: 1 addition & 1 deletion README.HIGHCHARTS
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
The HighCharts graphing library is a kick ass JS graping library, free for non-commercial use.
The HighCharts graphing library is a kick ass JS graphing library, free for non-commercial use.

Since you may be using XHProf to aid the performance of a commercial site, a single blanket license
has already been purchased. You can use XHProf with Highcharts on any site, you do _not_ need to
Expand Down
2 changes: 1 addition & 1 deletion README.markdown
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
This branch/clone/whatever git calls it of the official Facebook GUI does a few things:

* It includes a header.php document you can use with PHP's
auto\_prepend\_file directive. It sets up profiling by initilizing a few variables, and settting register_shutdown_function with the footer. Once started profiles are done
auto\_prepend\_file directive. It sets up profiling by initilizing a few variables, and setting register_shutdown_function with the footer. Once started profiles are done
when requested (?\_profile=1), or randomly. Profiled pages display a link to
their profile results at the bottom of the page (this can be disabled on a
blacklist based for specific documents. e.g. pages generating XML, images,
Expand Down
32 changes: 21 additions & 11 deletions external/footer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,25 @@
define('XHPROF_LIB_ROOT', dirname(dirname(__FILE__)) . '/xhprof_lib');
}

if (extension_loaded('xhprof') && $_xhprof['doprofile'] === true) {
$profiler_namespace = $_xhprof['namespace']; // namespace for your application
$xhprof_data = xhprof_disable();
$xhprof_runs = new XHProfRuns_Default();
$run_id = $xhprof_runs->save_run($xhprof_data, $profiler_namespace, null, $_xhprof);
if ($_xhprof['display'] === true && PHP_SAPI != 'cli')
{
// url to the XHProf UI libraries (change the host name and path)
$profiler_url = sprintf($_xhprof['url'].'/index.php?run=%s&source=%s', $run_id, $profiler_namespace);
echo '<a href="'. $profiler_url .'" target="_blank">Profiler output</a>';
}
if ($_xhprof['doprofile'] === true && extension_loaded('tideways')) {
$profiler_namespace = $_xhprof['namespace']; // namespace for your application
$xhprof_data = tideways_disable();
$xhprof_runs = new XHProfRuns_Default();
$run_id = $xhprof_runs->save_run($xhprof_data, $profiler_namespace, null, $_xhprof);
if (PHP_SAPI !== 'cli' && $_xhprof['display'] === true) {
// url to the XHProf UI libraries (change the host name and path)
$profiler_url = sprintf($_xhprof['url'].'/index.php?run=%s&source=%s', $run_id, $profiler_namespace);
echo '<a href="'. $profiler_url .'" target="_blank">Profiler output</a>';
}
}
if ($_xhprof['doprofile'] === true && extension_loaded('xhprof')) {
$profiler_namespace = $_xhprof['namespace']; // namespace for your application
$xhprof_data = xhprof_disable();
$xhprof_runs = new XHProfRuns_Default();
$run_id = $xhprof_runs->save_run($xhprof_data, $profiler_namespace, null, $_xhprof);
if (PHP_SAPI !== 'cli' && $_xhprof['display'] === true) {
// url to the XHProf UI libraries (change the host name and path)
$profiler_url = sprintf($_xhprof['url'].'/index.php?run=%s&source=%s', $run_id, $profiler_namespace);
echo '<a href="'. $profiler_url .'" target="_blank">Profiler output</a>';
}
}
162 changes: 79 additions & 83 deletions external/header.php
Original file line number Diff line number Diff line change
@@ -1,133 +1,129 @@
<?php
if (PHP_SAPI == 'cli') {
$_SERVER['REMOTE_ADDR'] = null;
$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'];
$_SERVER['REMOTE_ADDR'] = NULL;
$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'];
}

include(dirname(__FILE__) . '/../xhprof_lib/config.php');

//I'm Magic :)
class visibilitator
{
public static function __callstatic($name, $arguments)
{
// I'm Magic :)
class visibilitator {
public static function __callstatic($name, $arguments) {
$func_name = array_shift($arguments);
//var_dump($name);
//var_dump("arguments" ,$arguments);
//var_dump($func_name);
if (is_array($func_name))
{
if (is_array($func_name)) {
list($a, $b) = $func_name;
if (count($arguments) == 0)
{
if (count($arguments) == 0) {
$arguments = $arguments[0];
}
return call_user_func_array(array($a, $b), $arguments);
//echo "array call -> $b ($arguments)";
}else {
// echo "array call -> $b ($arguments)";
} else {
call_user_func_array($func_name, $arguments);
}
}
}

// Only users from authorized IP addresses may control Profiling
if ($controlIPs === false || in_array($_SERVER['REMOTE_ADDR'], $controlIPs) || PHP_SAPI == 'cli')
{
if (isset($_GET['_profile']))
{
//Give them a cookie to hold status, and redirect back to the same page
setcookie('_profile', $_GET['_profile']);
$newURI = str_replace(array('_profile=1','_profile=0'), '', $_SERVER['REQUEST_URI']);
header("Location: $newURI");
exit;
}
if ($controlIPs === FALSE || in_array($_SERVER['REMOTE_ADDR'], $controlIPs) || PHP_SAPI == 'cli') {
if (isset($_GET['_profile'])) {
// Give them a cookie to hold status, and redirect back to the same page
setcookie($_xhprof['cookieName'], $_GET['_profile']);
$newURI = str_replace(array('_profile=1', '_profile=0'), '', $_SERVER['REQUEST_URI']);
header("Location: $newURI");
exit;
}

if (isset($_COOKIE['_profile']) && $_COOKIE['_profile'] || PHP_SAPI == 'cli' && ((isset($_SERVER['XHPROF_PROFILE']) && $_SERVER['XHPROF_PROFILE']) || (isset($_ENV['XHPROF_PROFILE']) && $_ENV['XHPROF_PROFILE'])))
{
$_xhprof['display'] = true;
$_xhprof['doprofile'] = true;
$_xhprof['type'] = 1;
}
if (isset($_COOKIE[$_xhprof['cookieName']]) && $_COOKIE[$_xhprof['cookieName']] || PHP_SAPI == 'cli' && ((isset($_SERVER['XHPROF_PROFILE']) && $_SERVER['XHPROF_PROFILE']) || (isset($_ENV['XHPROF_PROFILE']) && $_ENV['XHPROF_PROFILE']))) {
$_xhprof['doprofile'] = TRUE;
$_xhprof['type'] = 1;
}
}


//Certain URLs should never have a link displayed. Think images, xml, etc.
foreach($exceptionURLs as $url)
{
if (stripos($_SERVER['REQUEST_URI'], $url) !== FALSE)
{
$_xhprof['display'] = false;
header('X-XHProf-No-Display: Trueness');
break;
}
// Certain URLs should never have a link displayed. Think images, xml, etc.
foreach ($exceptionURLs as $url) {
if (stripos($_SERVER['REQUEST_URI'], $url) !== FALSE) {
$_xhprof['display'] = FALSE;
header('X-XHProf-No-Display: Trueness');
break;
}
}
unset($exceptionURLs);

//Certain urls should have their POST data omitted. Think login forms, other privlidged info
$_xhprof['savepost'] = true;
foreach ($exceptionPostURLs as $url)
{
if (stripos($_SERVER['REQUEST_URI'], $url) !== FALSE)
{
$_xhprof['savepost'] = false;
break;
}
// Certain urls should have their POST data omitted. Think login forms, other privlidged info
$_xhprof['savepost'] = TRUE;
foreach ($exceptionPostURLs as $url) {
if (stripos($_SERVER['REQUEST_URI'], $url) !== FALSE) {
$_xhprof['savepost'] = FALSE;
break;
}
}
unset($exceptionPostURLs);

//Determine wether or not to profile this URL randomly
if ($_xhprof['doprofile'] === false)
{
//Profile weighting, one in one hundred requests will be profiled without being specifically requested
if (rand(1, $weight) == 1)
{
$_xhprof['doprofile'] = true;
$_xhprof['type'] = 0;
}
// Determine wether or not to profile this URL randomly
if ($_xhprof['doprofile'] === FALSE) {
// Profile weighting, one in one hundred requests will be profiled without being specifically requested
if (rand(1, $weight) == 1) {
$_xhprof['doprofile'] = TRUE;
$_xhprof['type'] = 0;
}
}
unset($weight);

// Certain URLS should never be profiled.
foreach($ignoreURLs as $url){
if (stripos($_SERVER['REQUEST_URI'], $url) !== FALSE)
{
$_xhprof['doprofile'] = false;
break;
}
foreach ($ignoreURLs as $url) {
if (stripos($_SERVER['REQUEST_URI'], $url) !== FALSE) {
$_xhprof['doprofile'] = FALSE;
break;
}
}
unset($ignoreURLs);

unset($url);

// Certain domains should never be profiled.
foreach($ignoreDomains as $domain){
if (stripos($_SERVER['HTTP_HOST'], $domain) !== FALSE)
{
$_xhprof['doprofile'] = false;
break;
}
foreach ($ignoreDomains as $domain) {
if (stripos($_SERVER['HTTP_HOST'], $domain) !== FALSE) {
$_xhprof['doprofile'] = FALSE;
break;
}
}
unset($ignoreDomains);
unset($domain);

//Display warning if extension not available
if (extension_loaded('xhprof') && $_xhprof['doprofile'] === true) {
include_once dirname(__FILE__) . '/../xhprof_lib/utils/xhprof_lib.php';
include_once dirname(__FILE__) . '/../xhprof_lib/utils/xhprof_runs.php';
if (isset($ignoredFunctions) && is_array($ignoredFunctions) && !empty($ignoredFunctions)) {
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY, array('ignored_functions' => $ignoredFunctions));
} else {
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
}
}elseif(!extension_loaded('xhprof') && $_xhprof['display'] === true)
{
$message = 'Warning! Unable to profile run, xhprof extension not loaded';
trigger_error($message, E_USER_WARNING);
// Display warning if extension not available
if ($_xhprof['doprofile'] === TRUE && extension_loaded('tideways')) {
include_once dirname(__FILE__) . '/../xhprof_lib/utils/xhprof_lib.php';
include_once dirname(__FILE__) . '/../xhprof_lib/utils/xhprof_runs.php';
if (isset($ignoredFunctions) && is_array($ignoredFunctions) && !empty($ignoredFunctions)) {
tideways_enable(TIDEWAYS_FLAGS_CPU + TIDEWAYS_FLAGS_MEMORY, array('ignored_functions' => $ignoredFunctions));
} else {
tideways_enable(TIDEWAYS_FLAGS_CPU + TIDEWAYS_FLAGS_MEMORY);
}
} elseif ($_xhprof['display'] === TRUE && !extension_loaded('tideways')) {
$message = 'Warning! Unable to profile run, xhprof extension not loaded';
trigger_error($message, E_USER_WARNING);
}

if ($_xhprof['doprofile'] === TRUE && extension_loaded('xhprof')) {
include_once dirname(__FILE__) . '/../xhprof_lib/utils/xhprof_lib.php';
include_once dirname(__FILE__) . '/../xhprof_lib/utils/xhprof_runs.php';
if (isset($ignoredFunctions) && is_array($ignoredFunctions) && !empty($ignoredFunctions)) {
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY, array('ignored_functions' => $ignoredFunctions));
} else {
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
}
} elseif ($_xhprof['display'] === TRUE && !extension_loaded('xhprof')) {
$message = 'Warning! Unable to profile run, xhprof extension not loaded';
trigger_error($message, E_USER_WARNING);
}

function xhprof_shutdown_function() {
global $_xhprof;
require dirname(__FILE__).'/footer.php';
global $_xhprof;
require dirname(__FILE__) . '/footer.php';
}

register_shutdown_function('xhprof_shutdown_function');
48 changes: 48 additions & 0 deletions xhprof_html/JavaScript/CallGraph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*jshint bitwise:true, curly:true, eqeqeq:true, forin:true, globalstrict: true,
latedef:true, noarg:true, noempty:true, nonew:true, undef:true, maxlen:256,
strict:true, trailing:true, boss:true, browser:true, devel:true, jquery:true */
/*global chrome, safari, SAFARI, openTab, Ember, DS, localize, Raphael */
'use strict';

jQuery(document).ready(function($) {
$('.loading').show();
$.ajax({
url: './callGraphRenderer.php' + window.location.search,
cache: false
})
.done(function(html) {
$('.loading').hide();
$('#callGraph').append(html);
$('#callGraph svg').panzoom({
$zoomIn: $('.zoomIn'),
$zoomOut: $('.zoomOut'),
$zoomRange: $('.zoomRange'),
$reset: $('.zoomReset'),
increment: 0.05,
rangeStep: 0.05,
startTransform: 'matrix(0.50, 0, 0, 0.50, -' +
Math.ceil($('#callGraph svg').width() / 2) + ', -' +
Math.ceil($('#callGraph svg').height() / 4.5) + ')',
minScale: 0.1,
maxScale: 1
}).panzoom('zoom');
$('#callGraph').on('mousewheel.focal', function(e) {
e.preventDefault();
var delta = e.delta || e.originalEvent.wheelDelta;
var zoomOut = delta ? delta < 0 : e.originalEvent.deltaY > 0;
$('#callGraph svg').panzoom('zoom', zoomOut, {
increment: 0.01,
focal: e
});
});
$('#callGraph').on('dblclick', function(e) {
$('#callGraph svg').panzoom(
'zoom',
{
scale: Number($('.zoomRange').val()) + 0.05,
focal: e
}
);
});
});
});
75 changes: 75 additions & 0 deletions xhprof_html/JavaScript/Main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*jshint bitwise:true, curly:true, eqeqeq:true, forin:true, globalstrict: true,
latedef:true, noarg:true, noempty:true, nonew:true, undef:true, maxlen:256,
strict:true, trailing:true, boss:true, browser:true, devel:true, jquery:true */
/*global chrome, safari, SAFARI, openTab, Ember, DS, localize */
'use strict';

jQuery(document).ready(function($) {
jQuery.extend(jQuery.fn.dataTableExt.oSort, {
'title-numeric-pre': function(a) {
var x = a.match(/title="*(-?[0-9\.]+)/)[1];
return parseFloat(x);
},

'title-numeric-asc': function(a, b) {
return ((a < b) ? -1 : ((a > b) ? 1 : 0));
},

'title-numeric-desc': function(a, b) {
return ((a < b) ? 1 : ((a > b) ? -1 : 0));
}
});
var sortableTable = $('.tablesorter').dataTable({
'bFilter': true,
'iDisplayLength': 50,
/* Disable initial sort */
'aaSorting': [],
'aoColumnDefs': [{
'sType': 'title-numeric',
'aTargets': ['title-numeric']
}],
'sDom': 'RClfrtip'
});
new FixedHeader(document.getElementById('box-table-a'));
// This kills scrolling performance for large tables
//$('#box-table-a').stickyTableHeaders();
$('#domainFilterDomain').change(function() {
$('#domainFilter').submit();
});
$('.filterByDomain').click(function(e) {
e.preventDefault();
$('#domainFilterDomain').val(e.target.innerText);
$('#domainFilter').submit();
});
$('#serverFilterServer').change(function() {
$('#serverFilter').submit();
});
/*
$('[title!=""]').qtip({
style: {
classes: 'qtip-rounded qtip-shadow'
},
title: {
button: true
},
show: {
solo: true,
effect: function() {
$(this).fadeTo(200, 1);
}

},
hide: {
event: false,
inactive: 1500,
effect: function() {
$(this).fadeTo(200, 0);
}
},
position: {
my: 'bottom cencter',
at: 'top cencter'
}
});
*/
});
Loading