discomfitor pushed a commit to branch master.
commit c8d615f155020b5bfafbbcbc575dfed74744b98c
Author: Mike Blumenkrantz <[email protected]>
Date: Mon May 20 14:00:06 2013 +0100
comp shape updates:
* move all shape rectangle stuff into e_container_shape
* use input rects for cutting comp shape when available
* set shape changed and render queue when container shape change callbacks
are called
* create fewer comp wins (small memory improvement)
---
src/bin/e_border.c | 18 ++----
src/bin/e_border.h | 2 -
src/bin/e_comp.c | 158 ++++++++++++++++++++++++++------------------------
src/bin/e_comp.h | 2 -
src/bin/e_container.c | 60 +++++++++----------
src/bin/e_container.h | 12 ++--
6 files changed, 127 insertions(+), 125 deletions(-)
diff --git a/src/bin/e_border.c b/src/bin/e_border.c
index 4bbebcf..5a004dc 100644
--- a/src/bin/e_border.c
+++ b/src/bin/e_border.c
@@ -4708,8 +4708,6 @@ _e_border_free(E_Border *bd)
bd->cur_mouse_action = NULL;
}
- E_FREE(bd->shape_rects);
- bd->shape_rects_num = 0;
/*
if (bd->dangling_ref_check)
{
@@ -7679,6 +7677,8 @@ _e_border_eval0(E_Border *bd)
bd->client.border.changed = 1;
}
ecore_x_window_shape_rectangles_set(bd->win, rects, num);
+ bd->changes.shape_input = 0;
+ e_container_shape_input_rects_set(bd->shape, NULL, 0);
}
free(rects);
}
@@ -7731,8 +7731,8 @@ _e_border_eval0(E_Border *bd)
bd->client.border.changed = 1;
}
ecore_x_window_shape_input_rectangles_set(bd->win, rects,
num);
+ e_container_shape_input_rects_set(bd->shape,
(Eina_Rectangle*)rects, num);
}
- free(rects);
}
else
{
@@ -8756,11 +8756,11 @@ _e_border_eval(E_Border *bd)
int changed;
changed = 1;
- if ((num == bd->shape_rects_num) && (bd->shape_rects))
+ if ((num == bd->shape->shape_rects_num) &&
(bd->shape->shape_rects))
{
int i;
- orects = bd->shape_rects;
+ orects = (Ecore_X_Rectangle*)bd->shape->shape_rects;
changed = 0;
for (i = 0; i < num; i++)
{
@@ -8793,19 +8793,13 @@ _e_border_eval(E_Border *bd)
{
if (bd->client.shaped)
e_container_shape_solid_rect_set(bd->shape, 0, 0, 0, 0);
- E_FREE(bd->shape_rects);
- bd->shape_rects = rects;
- bd->shape_rects_num = num;
- e_container_shape_rects_set(bd->shape, rects, num);
+ e_container_shape_rects_set(bd->shape,
(Eina_Rectangle*)rects, num);
}
else
free(rects);
}
else
{
- E_FREE(bd->shape_rects);
- bd->shape_rects = NULL;
- bd->shape_rects_num = 0;
e_container_shape_rects_set(bd->shape, NULL, 0);
}
bd->need_shape_export = 0;
diff --git a/src/bin/e_border.h b/src/bin/e_border.h
index 5ec2b16..3364d7a 100644
--- a/src/bin/e_border.h
+++ b/src/bin/e_border.h
@@ -634,8 +634,6 @@ struct _E_Border
Ecore_Poller *ping_poller;
Ecore_Timer *kill_timer;
E_Border_Move_Intercept_Cb move_intercept_cb;
- int shape_rects_num;
- Ecore_X_Rectangle *shape_rects;
E_Remember *remember;
E_Border *modal;
diff --git a/src/bin/e_comp.c b/src/bin/e_comp.c
index d5f2e30..713db75 100644
--- a/src/bin/e_comp.c
+++ b/src/bin/e_comp.c
@@ -224,24 +224,24 @@ _e_comp_fullscreen_check(E_Comp *c)
}
static inline Eina_Bool
-_e_comp_shaped_check(int w, int h, const Ecore_X_Rectangle *rects, int num)
+_e_comp_shaped_check(int w, int h, const Eina_Rectangle *rects, int num)
{
if ((!rects) || (num < 1)) return EINA_FALSE;
if (num > 1) return EINA_TRUE;
if ((rects[0].x == 0) && (rects[0].y == 0) &&
- ((int)rects[0].width == w) && ((int)rects[0].height == h))
+ ((int)rects[0].w == w) && ((int)rects[0].h == h))
return EINA_FALSE;
return EINA_TRUE;
}
static inline Eina_Bool
-_e_comp_win_shaped_check(const E_Comp_Win *cw, const Ecore_X_Rectangle *rects,
int num)
+_e_comp_win_shaped_check(const E_Comp_Win *cw, const Eina_Rectangle *rects,
int num)
{
return _e_comp_shaped_check(cw->w, cw->h, rects, num);
}
static void
-_e_comp_win_shape_rectangles_apply(E_Comp_Win *cw, const Ecore_X_Rectangle
*rects, int num)
+_e_comp_win_shape_rectangles_apply(E_Comp_Win *cw, const Eina_Rectangle
*rects, int num)
{
Eina_List *l;
Evas_Object *o;
@@ -286,7 +286,7 @@ _e_comp_win_shape_rectangles_apply(E_Comp_Win *cw, const
Ecore_X_Rectangle *rect
int rx, ry, rw, rh;
rx = rects[i].x; ry = rects[i].y;
- rw = rects[i].width; rh = rects[i].height;
+ rw = rects[i].w; rh = rects[i].h;
E_RECTS_CLIP_TO_RECT(rx, ry, rw, rh, 0, 0, w, h);
sp = spix + (w * ry) + rx;
for (py = 0; py < rh; py++)
@@ -529,58 +529,48 @@ _e_comp_win_update(E_Comp_Win *cw)
Eina_List *l;
Evas_Object *o;
E_Comp_Render_Update_Rect *r;
- int i;
+ int i, num;
int pw, ph;
int pshaped = cw->shaped;
+ Eina_Rectangle *rects;
DBG("UPDATE [0x%x] pm = %x", cw->win, cw->pixmap);
if (conf->grab) ecore_x_grab();
cw->update = 0;
pw = cw->pw, ph = cw->ph;
- if (cw->argb)
+ if (cw->shape_changed)
{
- if (cw->rects)
- {
- free(cw->rects);
- cw->rects = NULL;
- cw->rects_num = 0;
- }
- }
- else
- {
- if (cw->shape_changed)
+ if (cw->free_shape)
{
- if (cw->rects)
- {
- free(cw->rects);
- cw->rects = NULL;
- cw->rects_num = 0;
- }
ecore_x_pixmap_geometry_get(cw->win, NULL, NULL, &(cw->w),
&(cw->h));
- cw->rects = ecore_x_window_shape_rectangles_get(cw->win,
&(cw->rects_num));
- if (cw->rects)
- {
- for (i = 0; i < cw->rects_num; i++)
- {
- E_RECTS_CLIP_TO_RECT(cw->rects[i].x, cw->rects[i].y,
- cw->rects[i].width, cw->rects[i].height, 0, 0,
(int)cw->w, (int)cw->h);
- }
- }
- if (!_e_comp_win_shaped_check(cw, cw->rects, cw->rects_num))
+ rects =
(Eina_Rectangle*)ecore_x_window_shape_rectangles_get(cw->win, &num);
+ e_container_shape_rects_set(cw->shape, rects, num);
+ if (cw->shape->shape_rects)
+ e_container_shape_input_rects_set(cw->shape, NULL, 0);
+ else
{
- E_FREE(cw->rects);
- cw->rects_num = 0;
+ rects =
(Eina_Rectangle*)ecore_x_window_shape_input_rectangles_get(cw->win, &num);
+ e_container_shape_input_rects_set(cw->shape, rects, num);
}
- if ((cw->rects) && (!cw->shaped))
+ }
+ if (cw->shape->shape_rects)
+ {
+ for (i = 0; i < cw->shape->shape_rects_num; i++)
{
- cw->shaped = 1;
+ E_RECTS_CLIP_TO_RECT(cw->shape->shape_rects[i].x,
cw->shape->shape_rects[i].y,
+ cw->shape->shape_rects[i].w, cw->shape->shape_rects[i].h,
0, 0, (int)cw->w, (int)cw->h);
}
- else if ((!cw->rects) && (cw->shaped))
+ }
+ if (cw->shape->shape_input_rects)
+ {
+ for (i = 0; i < cw->shape->shape_input_rects_num; i++)
{
- cw->shaped = 0;
+ E_RECTS_CLIP_TO_RECT(cw->shape->shape_input_rects[i].x,
cw->shape->shape_input_rects[i].y,
+ cw->shape->shape_input_rects[i].w,
cw->shape->shape_input_rects[i].h, 0, 0, (int)cw->w, (int)cw->h);
}
}
+ cw->shaped = _e_comp_win_shaped_check(cw, cw->shape->shape_rects,
cw->shape->shape_rects_num);
}
if (((!cw->pixmap) || (cw->needpix)) &&
@@ -671,7 +661,7 @@ _e_comp_win_update(E_Comp_Win *cw)
// was cw->w / cw->h
// evas_object_resize(cw->effect_obj, cw->pw, cw->ph);
if ((cw->c->gl) && (conf->texture_from_pixmap) &&
- (!cw->shaped) && (!cw->rects) && (cw->pixmap))
+ (!cw->shaped) && (cw->pixmap))
{
/* #ifdef HAVE_WAYLAND_CLIENTS */
/* DBG("DEBUG - pm now %x", e_comp_wl_pixmap_get(cw->win)); */
@@ -849,15 +839,8 @@ _e_comp_win_update(E_Comp_Win *cw)
}
}
free(r);
- if (cw->shaped)
- {
- _e_comp_win_shape_rectangles_apply(cw, cw->rects,
cw->rects_num);
- }
- else
- {
- if (cw->shape_changed)
- _e_comp_win_shape_rectangles_apply(cw, cw->rects,
cw->rects_num);
- }
+ if (cw->shaped || cw->shape_changed)
+ _e_comp_win_shape_rectangles_apply(cw, cw->shape->shape_rects,
cw->shape->shape_rects_num);
cw->shape_changed = 0;
}
else
@@ -2124,6 +2107,19 @@ _e_comp_win_bd_setup(E_Comp_Win *cw, E_Border *bd)
cw->depth = cw->bd->client.initial_attributes.depth;
}
+static void
+_e_comp_win_shape_init(E_Comp_Win *cw, int w, int h)
+{
+ int i;
+
+ for (i = 0; i < cw->shape->shape_rects_num; i++)
+ E_RECTS_CLIP_TO_RECT(cw->shape->shape_rects[i].x,
cw->shape->shape_rects[i].y,
+ cw->shape->shape_rects[i].w,
cw->shape->shape_rects[i].h, 0, 0, w, h);
+
+ if (_e_comp_shaped_check(w, h, cw->shape->shape_rects,
cw->shape->shape_rects_num))
+ cw->shape_changed = 1;
+}
+
static E_Comp_Win *
_e_comp_win_add(E_Comp *c, Ecore_X_Window win, E_Border *bd)
{
@@ -2211,9 +2207,6 @@ _e_comp_win_add(E_Comp *c, Ecore_X_Window win, E_Border
*bd)
cw->inhash = 1;
if ((!cw->input_only) && (!cw->invalid))
{
- Ecore_X_Rectangle *rects;
- int num;
-
cw->damage = ecore_x_damage_new
(cw->win, ECORE_X_DAMAGE_REPORT_DELTA_RECTANGLES);
eina_hash_add(damages, e_util_winid_str_get(cw->damage), cw);
@@ -2239,23 +2232,10 @@ _e_comp_win_add(E_Comp *c, Ecore_X_Window win, E_Border
*bd)
evas_object_show(cw->obj);
ecore_x_window_shape_events_select(cw->win, 1);
- rects = ecore_x_window_shape_rectangles_get(cw->win, &num);
- if (rects)
- {
- int i;
-
- for (i = 0; i < num; i++)
- E_RECTS_CLIP_TO_RECT(rects[i].x, rects[i].y,
- rects[i].width, rects[i].height, 0, 0, w,
h);
-
- if (_e_comp_shaped_check(w, h, rects, num))
- cw->shape_changed = 1;
-
- free(rects);
- }
if (cw->bd)
{
+ _e_comp_win_shape_init(cw, w, h);
evas_object_data_set(cw->shobj, "border", cw->bd);
evas_object_data_set(cw->effect_obj, "border", cw->bd);
#ifdef BORDER_ZOOMAPS
@@ -2356,7 +2336,6 @@ _e_comp_win_del(E_Comp_Win *cw)
E_FREE_FUNC(cw->up, e_comp_render_update_free);
DBG(" [0x%x] del", cw->win);
- E_FREE(cw->rects);
if (cw->update_timeout)
{
ecore_timer_del(cw->update_timeout);
@@ -2935,11 +2914,12 @@ _e_comp_create(void *data EINA_UNUSED, int type
EINA_UNUSED, void *event)
c = _e_comp_find(ev->parent);
if (!c) return ECORE_CALLBACK_PASS_ON;
- if (_e_comp_win_find(ev->win)) return ECORE_CALLBACK_PASS_ON;
if (c->win == ev->win) return ECORE_CALLBACK_PASS_ON;
if (c->ee_win == ev->win) return ECORE_CALLBACK_PASS_ON;
if (c->man->root == ev->win) return ECORE_CALLBACK_PASS_ON;
if (_e_comp_ignore_find(ev->win)) return ECORE_CALLBACK_PASS_ON;
+ if (_e_comp_win_find(ev->win)) return ECORE_CALLBACK_PASS_ON;
+ if (!ev->override) return ECORE_CALLBACK_PASS_ON;
cw = _e_comp_win_add(c, ev->win, NULL);
if (!cw) return ECORE_CALLBACK_RENEW;
_e_comp_win_configure(cw, ev->x, ev->y, ev->w, ev->h, ev->border);
@@ -2947,6 +2927,8 @@ _e_comp_create(void *data EINA_UNUSED, int type
EINA_UNUSED, void *event)
{
Eina_List *l;
E_Container *con;
+ Eina_Rectangle *rects;
+ int num;
EINA_LIST_FOREACH(c->man->containers, l, con)
{
@@ -2955,8 +2937,17 @@ _e_comp_create(void *data EINA_UNUSED, int type
EINA_UNUSED, void *event)
break;
}
if (!cw->shape) cw->shape =
e_container_shape_add(eina_list_data_get(c->man->containers));
- e_container_shape_move(cw->shape, ev->x, ev->y);
e_container_shape_resize(cw->shape, ev->w, ev->h);
+ rects = (Eina_Rectangle*)ecore_x_window_shape_rectangles_get(cw->win,
&num);
+ e_container_shape_rects_set(cw->shape, rects, num);
+ if (cw->shape->shape_rects)
+ e_container_shape_input_rects_set(cw->shape, NULL, 0);
+ else
+ {
+ rects =
(Eina_Rectangle*)ecore_x_window_shape_input_rectangles_get(cw->win, &num);
+ e_container_shape_input_rects_set(cw->shape, rects, num);
+ }
+ _e_comp_win_shape_init(cw, ev->w, ev->h);
}
if (cw->shape) cw->shape->comp_win = cw;
return ECORE_CALLBACK_PASS_ON;
@@ -3749,10 +3740,10 @@
_e_comp_shapes_update_comp_win_shape_comp_helper(E_Comp_Win *cw, Eina_Tiler *tb)
INF("COMP WIN: %u", cw->win);
#endif
- if (cw->rects)
+ if (cw->shape->shape_input_rects || cw->shape->shape_rects)
{
- int num;
- Ecore_X_Rectangle *rect;
+ int num, tot;
+ Eina_Rectangle *rect, *rects;
/* add the frame */
if (cw->bd)
@@ -3777,13 +3768,15 @@
_e_comp_shapes_update_comp_win_shape_comp_helper(E_Comp_Win *cw, Eina_Tiler *tb)
if (cw->bd->client_inset.b)
{
eina_tiler_rect_add(tb, &(Eina_Rectangle){cw->bd->x,
cw->bd->y + cw->bd->client_inset.t + cw->bd->client.h, cw->bd->w,
cw->bd->client_inset.b});
- SHAPE_INF("ADD: %d,%d@%dx%d", cw->bd->x, cw->bd->y,
cw->bd->w, cw->bd->h);
+ SHAPE_INF("ADD: %d,%d@%dx%d", cw->bd->x, cw->bd->y +
cw->bd->client_inset.t + cw->bd->client.h, cw->bd->w, cw->bd->client_inset.b);
}
}
}
- for (num = 0, rect = cw->rects; num < cw->rects_num; num++, rect++)
+ rects = cw->shape->shape_rects ?: cw->shape->shape_input_rects;
+ tot = cw->shape->shape_rects_num ?: cw->shape->shape_input_rects_num;
+ for (num = 0, rect = rects; num < tot; num++, rect++)
{
- x = rect->x, y = rect->y, w = rect->width, h = rect->height;
+ x = rect->x, y = rect->y, w = rect->w, h = rect->h;
if (cw->bd)
{
x += cw->bd->x, y += cw->bd->y;
@@ -3988,6 +3981,10 @@ _e_comp_shapes_update(void *data, E_Container_Shape *es,
E_Container_Shape_Chang
case E_CONTAINER_SHAPE_HIDE:
case E_CONTAINER_SHAPE_DEL:
break;
+ case E_CONTAINER_SHAPE_RECTS:
+ case E_CONTAINER_SHAPE_INPUT_RECTS:
+ es->comp_win->shape_changed = 1;
+ _e_comp_win_render_queue(es->comp_win);
default:
/* any other changes only matter if the
* object is visible
@@ -4105,6 +4102,17 @@ _e_comp_populate(E_Comp *c)
e_container_shape_move(cw->shape, x, y);
e_container_shape_resize(cw->shape, w, h);
}
+ if (cw->free_shape)
+ {
+ Eina_Rectangle *rects;
+ int rnum;
+
+ rects =
(Eina_Rectangle*)ecore_x_window_shape_rectangles_get(cw->win, &rnum);
+ e_container_shape_rects_set(cw->shape, rects, rnum);
+ rects =
(Eina_Rectangle*)ecore_x_window_shape_input_rectangles_get(cw->win, &rnum);
+ e_container_shape_input_rects_set(cw->shape, rects, rnum);
+ _e_comp_win_shape_init(cw, w, h);
+ }
if ((!cw->bd) && (ecore_x_window_visible_get(wins[i])))
_e_comp_win_show(cw);
}
diff --git a/src/bin/e_comp.h b/src/bin/e_comp.h
index baed8db..98a8068 100644
--- a/src/bin/e_comp.h
+++ b/src/bin/e_comp.h
@@ -139,8 +139,6 @@ struct _E_Comp_Win
Ecore_Timer *update_timeout; // max time between damage and "done"
event
Ecore_Timer *ready_timeout; // max time on show (new window draw)
to wait for window contents to be ready if sync protocol not handled. this is
fallback.
int dmg_updates; // num of damage event updates since a
redirect
- Ecore_X_Rectangle *rects; // shape rects... if shaped :(
- int rects_num; // num rects above
Ecore_X_Pixmap cache_pixmap; // the cached pixmap (1/nth the
dimensions)
int cache_w, cache_h; // cached pixmap size
diff --git a/src/bin/e_container.c b/src/bin/e_container.c
index 724d2da..abcf1ce 100644
--- a/src/bin/e_container.c
+++ b/src/bin/e_container.c
@@ -450,51 +450,50 @@ e_container_shape_change_callback_del(E_Container *con,
E_Container_Shape_Cb fun
}
}
-EAPI Eina_List *
-e_container_shape_rects_get(E_Container_Shape *es)
+EAPI void
+e_container_shape_rects_set(E_Container_Shape *es, Eina_Rectangle *rects, int
num)
{
- E_OBJECT_CHECK_RETURN(es, NULL);
- E_OBJECT_TYPE_CHECK_RETURN(es, E_CONTAINER_SHAPE_TYPE, NULL);
- return es->shape;
+ E_OBJECT_CHECK(es);
+ E_OBJECT_TYPE_CHECK(es, E_CONTAINER_SHAPE_TYPE);
+ E_FREE(es->shape_rects);
+ es->shape_rects_num = 0;
+ if ((rects) && (num == 1) &&
+ (rects[0].x == 0) &&
+ (rects[0].y == 0) &&
+ ((int)rects[0].w == es->w) &&
+ ((int)rects[0].h == es->h))
+ {
+ /* do nothing */
+ }
+ else if (rects)
+ {
+ es->shape_rects = rects;
+ es->shape_rects_num = num;
+ }
+ _e_container_shape_change_call(es, E_CONTAINER_SHAPE_RECTS);
}
EAPI void
-e_container_shape_rects_set(E_Container_Shape *es, Ecore_X_Rectangle *rects,
int num)
+e_container_shape_input_rects_set(E_Container_Shape *es, Eina_Rectangle
*rects, int num)
{
- int i;
- E_Rect *r;
-
E_OBJECT_CHECK(es);
E_OBJECT_TYPE_CHECK(es, E_CONTAINER_SHAPE_TYPE);
- if (es->shape)
- {
- E_FREE_LIST(es->shape, free);
- es->shape = NULL;
- }
+ E_FREE(es->shape_input_rects);
+ es->shape_input_rects_num = 0;
if ((rects) && (num == 1) &&
(rects[0].x == 0) &&
(rects[0].y == 0) &&
- ((int)rects[0].width == es->w) &&
- ((int)rects[0].height == es->h))
+ ((int)rects[0].w == es->w) &&
+ ((int)rects[0].h == es->h))
{
/* do nothing */
}
else if (rects)
{
- for (i = 0; i < num; i++)
- {
- r = malloc(sizeof(E_Rect));
- if (r)
- {
- r->x = rects[i].x;
- r->y = rects[i].y;
- r->w = rects[i].width;
- r->h = rects[i].height;
- es->shape = eina_list_append(es->shape, r);
- }
- }
+ es->shape_input_rects = rects;
+ es->shape_input_rects_num = num;
}
- _e_container_shape_change_call(es, E_CONTAINER_SHAPE_RECTS);
+ _e_container_shape_change_call(es, E_CONTAINER_SHAPE_INPUT_RECTS);
}
EAPI void
@@ -939,7 +938,8 @@ static void
_e_container_shape_free(E_Container_Shape *es)
{
es->con->shapes = eina_list_remove(es->con->shapes, es);
- E_FREE_LIST(es->shape, free);
+ free(es->shape_rects);
+ free(es->shape_input_rects);
free(es);
}
diff --git a/src/bin/e_container.h b/src/bin/e_container.h
index 4b9c272..4619a75 100644
--- a/src/bin/e_container.h
+++ b/src/bin/e_container.h
@@ -8,7 +8,8 @@ typedef enum _E_Container_Shape_Change
E_CONTAINER_SHAPE_HIDE,
E_CONTAINER_SHAPE_MOVE,
E_CONTAINER_SHAPE_RESIZE,
- E_CONTAINER_SHAPE_RECTS
+ E_CONTAINER_SHAPE_RECTS,
+ E_CONTAINER_SHAPE_INPUT_RECTS
} E_Container_Shape_Change;
typedef struct _E_Container E_Container;
@@ -77,7 +78,10 @@ struct _E_Container_Shape
struct {
int x, y, w, h;
} solid_rect;
- Eina_List *shape;
+ int shape_rects_num;
+ Eina_Rectangle *shape_rects;
+ int shape_input_rects_num;
+ Eina_Rectangle *shape_input_rects;
};
struct _E_Container_Shape_Callback
@@ -127,8 +131,8 @@ EAPI void
e_container_shape_geometry_get(E_Container_Shape *es, in
EAPI E_Container *e_container_shape_container_get(E_Container_Shape *es);
EAPI void e_container_shape_change_callback_add(E_Container
*con, E_Container_Shape_Cb func, void *data);
EAPI void e_container_shape_change_callback_del(E_Container
*con, E_Container_Shape_Cb func, void *data);
-EAPI Eina_List *e_container_shape_rects_get(E_Container_Shape *es);
-EAPI void e_container_shape_rects_set(E_Container_Shape *es,
Ecore_X_Rectangle *rects, int num);
+EAPI void e_container_shape_rects_set(E_Container_Shape *es,
Eina_Rectangle *rects, int num);
+EAPI void e_container_shape_input_rects_set(E_Container_Shape
*es, Eina_Rectangle *rects, int num);
EAPI void e_container_shape_solid_rect_set(E_Container_Shape
*es, int x, int y, int w, int h);
EAPI void e_container_shape_solid_rect_get(E_Container_Shape
*es, int *x, int *y, int *w, int *h);
--
------------------------------------------------------------------------------
Try New Relic Now & We'll Send You this Cool Shirt
New Relic is the only SaaS-based application performance monitoring service
that delivers powerful full stack analytics. Optimize and monitor your
browser, app, & servers with just a few lines of code. Try New Relic
and get this awesome Nerd Life shirt! http://p.sf.net/sfu/newrelic_d2d_may