com.jitlogic.zico.client.views.traces.TraceSearchPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.jitlogic.zico.client.views.traces.TraceSearchPanel.java

Source

/**
 * Copyright 2012-2015 Rafal Lewczuk <rafal.lewczuk@jitlogic.com>
 * <p/>
 * This is free software. You can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * <p/>
 * This software is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * <p/>
 * You should have received a copy of the GNU General Public License
 * along with this software. If not, see <http://www.gnu.org/licenses/>.
 */
package com.jitlogic.zico.client.views.traces;

import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.ActionCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.cellview.client.*;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.*;
import com.google.gwt.view.client.CellPreviewEvent;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.SingleSelectionModel;
import com.google.inject.assistedinject.Assisted;
import com.jitlogic.zico.client.ClientUtil;
import com.jitlogic.zico.client.api.SystemService;
import com.jitlogic.zico.client.api.TraceDataService;
import com.jitlogic.zico.client.views.Shell;
import com.jitlogic.zico.client.resources.Resources;
import com.jitlogic.zico.client.inject.PanelFactory;
import com.jitlogic.zico.shared.data.*;
import com.jitlogic.zico.widgets.client.*;
import com.jitlogic.zico.widgets.client.MenuItem;
import org.fusesource.restygwt.client.Method;
import org.fusesource.restygwt.client.MethodCallback;

import javax.inject.Inject;
import javax.inject.Provider;
import java.util.*;

public class TraceSearchPanel extends Composite {
    interface TraceSearchPanelUiBinder extends UiBinder<Widget, TraceSearchPanel> {
    }

    private static TraceSearchPanelUiBinder ourUiBinder = GWT.create(TraceSearchPanelUiBinder.class);

    @UiField
    DockLayoutPanel panel;

    @UiField(provided = true)
    Resources resources;

    @UiField
    ToolButton btnErrors;

    @UiField
    ListBox lstTraceType;

    @UiField
    TextBox txtDuration;

    @UiField
    ToolButton btnEnableEql;

    @UiField
    TextBox txtFilter;

    @UiField
    TextBox txtStartDate;

    @UiField
    TextBox txtEndDate;

    @UiField
    ToolButton btnRunSearch;

    @UiField
    ToolButton btnFindMore;

    @UiField
    ToolButton btnClearFilters;

    @UiField(provided = true)
    DataGrid<TraceInfo> grid;

    public final static String RE_TIMESTAMP = "\\d{4}-\\d{2}-\\d{2}\\s*(\\d{2}:\\d{2}:\\d{2}(\\.\\d{1-3})?)?";

    private PanelFactory pf;

    private TraceDataService traceDataService;
    private SystemService systemService;

    private Provider<Shell> shell;

    private HostInfo host;

    private ListDataProvider<TraceInfo> data;
    private SingleSelectionModel<TraceInfo> selection;
    private ColumnSortEvent.ListHandler<TraceInfo> sortHandler;

    private TraceSearchTableBuilder rowBuilder;
    private Set<Long> expandedDetails = new HashSet<Long>();

    private int seqnum = 0;

    private PopupMenu contextMenu;
    private boolean moreResults;

    private String strTraceType;

    private MessageDisplay md;
    private final String MDS;

    @Inject
    public TraceSearchPanel(Provider<Shell> shell, TraceDataService traceDataService, SystemService systemService,
            PanelFactory pf, @Assisted HostInfo host, MessageDisplay md, @Assisted String traceName) {
        this.shell = shell;
        this.traceDataService = traceDataService;
        this.systemService = systemService;
        this.pf = pf;
        this.host = host;
        this.md = md;
        this.MDS = "TraceSearch:" + host.getName();

        this.resources = Resources.INSTANCE;

        createTraceGrid();

        ourUiBinder.createAndBindUi(this);

        createContextMenu();

        initWidget(panel);

        btnFindMore.setEnabled(false);
        btnErrors.setToggled(true);

        if (traceName != null) {
            lstTraceType.addItem(traceName);
            lstTraceType.setSelectedIndex(0);
        } else {
            loadTraceTypes();
        }

    }

    @UiHandler("lstTraceType")
    void onTraceTypeChange(ChangeEvent e) {
        strTraceType = lstTraceType.getItemText(lstTraceType.getSelectedIndex());
        refresh();
    }

    @UiHandler({ "txtDuration", "txtFilter", "txtStartDate" })
    void onTapEnter(KeyDownEvent e) {
        if (e.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
            refresh();
        }
    }

    @UiHandler("btnRunSearch")
    void onSearchClick(ClickEvent e) {
        refresh();
    }

    @UiHandler("btnClearFilters")
    void onClearClick(ClickEvent e) {
        txtFilter.setText("");
        txtDuration.setText("");
        btnErrors.setToggled(false);
        strTraceType = null;
        refresh();
    }

    private void createTraceGrid() {
        grid = new DataGrid<TraceInfo>(1024 * 1024, ZicoDataGridResources.INSTANCE, KEY_PROVIDER);
        selection = new SingleSelectionModel<TraceInfo>(KEY_PROVIDER);
        grid.setSelectionModel(selection);

        data = new ListDataProvider<TraceInfo>();
        data.addDataDisplay(grid);

        sortHandler = new ColumnSortEvent.ListHandler<TraceInfo>(data.getList());
        grid.addColumnSortHandler(sortHandler);

        Column<TraceInfo, TraceInfo> colExpander = new IdentityColumn<TraceInfo>(DETAIL_EXPANDER_CELL);
        grid.addColumn(colExpander, "#");
        grid.setColumnWidth(colExpander, 32, Style.Unit.PX);

        Column<TraceInfo, TraceInfo> colTraceClock = new IdentityColumn<TraceInfo>(TRACE_CLOCK_CELL);
        grid.addColumn(colTraceClock, new ResizableHeader<TraceInfo>("Clock", grid, colTraceClock));
        grid.setColumnWidth(colTraceClock, 140, Style.Unit.PX);

        colTraceClock.setSortable(true);
        sortHandler.setComparator(colTraceClock, new Comparator<TraceInfo>() {
            @Override
            public int compare(TraceInfo o1, TraceInfo o2) {
                return (int) (o1.getClock() - o2.getClock());
            }
        });

        Column<TraceInfo, TraceInfo> colTraceType = new IdentityColumn<TraceInfo>(TRACE_TYPE_CELL);
        grid.addColumn(colTraceType, new ResizableHeader<TraceInfo>("Type", grid, colTraceType));
        grid.setColumnWidth(colTraceType, 60, Style.Unit.PX);

        Column<TraceInfo, TraceInfo> colTraceDuration = new IdentityColumn<TraceInfo>(TRACE_DURATION_CELL);
        grid.addColumn(colTraceDuration, new ResizableHeader<TraceInfo>("Time", grid, colTraceDuration));
        grid.setColumnWidth(colTraceDuration, 64, Style.Unit.PX);
        colTraceDuration.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);

        colTraceDuration.setSortable(true);
        sortHandler.setComparator(colTraceDuration, new Comparator<TraceInfo>() {
            @Override
            public int compare(TraceInfo o1, TraceInfo o2) {
                return (int) ((o1.getExecutionTime() - o2.getExecutionTime()) / 1000000L);
            }
        });

        Column<TraceInfo, TraceInfo> colTraceCalls = new IdentityColumn<TraceInfo>(TRACE_CALLS_CELL);
        grid.addColumn(colTraceCalls, new ResizableHeader<TraceInfo>("Calls", grid, colTraceCalls));
        grid.setColumnWidth(colTraceCalls, 50, Style.Unit.PX);

        colTraceCalls.setSortable(true);
        sortHandler.setComparator(colTraceCalls, new Comparator<TraceInfo>() {
            @Override
            public int compare(TraceInfo o1, TraceInfo o2) {
                return (int) (o1.getCalls() - o2.getCalls());
            }
        });

        Column<TraceInfo, TraceInfo> colTraceErrors = new IdentityColumn<TraceInfo>(TRACE_ERRORS_CELL);
        grid.addColumn(colTraceErrors, new ResizableHeader<TraceInfo>("Errs", grid, colTraceErrors));
        grid.setColumnWidth(colTraceErrors, 50, Style.Unit.PX);

        colTraceErrors.setSortable(true);
        sortHandler.setComparator(colTraceErrors, new Comparator<TraceInfo>() {
            @Override
            public int compare(TraceInfo o1, TraceInfo o2) {
                return (int) (o1.getErrors() - o2.getErrors());
            }
        });

        Column<TraceInfo, TraceInfo> colTraceRecords = new IdentityColumn<TraceInfo>(TRACE_RECORDS_CELL);
        grid.addColumn(colTraceRecords, new ResizableHeader<TraceInfo>("Recs", grid, colTraceRecords));
        grid.setColumnWidth(colTraceRecords, 50, Style.Unit.PX);

        colTraceRecords.setSortable(true);
        sortHandler.setComparator(colTraceRecords, new Comparator<TraceInfo>() {
            @Override
            public int compare(TraceInfo o1, TraceInfo o2) {
                return (int) (o1.getRecords() - o2.getRecords());
            }
        });

        Column<TraceInfo, TraceInfo> colTraceDesc = new IdentityColumn<TraceInfo>(TRACE_NAME_CELL);
        grid.addColumn(colTraceDesc, "Description");
        grid.setColumnWidth(colTraceDesc, 100, Style.Unit.PCT);

        rowBuilder = new TraceSearchTableBuilder(grid, expandedDetails);
        grid.setTableBuilder(rowBuilder);

        grid.setSkipRowHoverStyleUpdate(true);
        grid.setSkipRowHoverFloatElementCheck(true);
        grid.setSkipRowHoverCheck(true);
        grid.setKeyboardSelectionPolicy(HasKeyboardSelectionPolicy.KeyboardSelectionPolicy.DISABLED);

        grid.addCellPreviewHandler(new CellPreviewEvent.Handler<TraceInfo>() {
            @Override
            public void onCellPreview(CellPreviewEvent<TraceInfo> event) {
                NativeEvent nev = event.getNativeEvent();
                String eventType = nev.getType();
                if ((BrowserEvents.KEYDOWN.equals(eventType) && nev.getKeyCode() == KeyCodes.KEY_ENTER)
                        || BrowserEvents.DBLCLICK.equals(nev.getType())) {
                    selection.setSelected(event.getValue(), true);
                    openDetailView();
                }
                if (BrowserEvents.CONTEXTMENU.equals(eventType)) {
                    selection.setSelected(event.getValue(), true);
                    if (selection.getSelectedObject() != null) {
                        contextMenu.setPopupPosition(event.getNativeEvent().getClientX(),
                                event.getNativeEvent().getClientY());
                        contextMenu.show();
                    }
                }
            }
        });

        grid.addDomHandler(new DoubleClickHandler() {
            @Override
            public void onDoubleClick(DoubleClickEvent event) {
                event.preventDefault();
            }
        }, DoubleClickEvent.getType());
        grid.addDomHandler(new ContextMenuHandler() {
            @Override
            public void onContextMenu(ContextMenuEvent event) {
                event.preventDefault();
            }
        }, ContextMenuEvent.getType());
    }

    private void createContextMenu() {
        contextMenu = new PopupMenu();

        MenuItem mnuMethodTree = new MenuItem("Method call tree", Resources.INSTANCE.methodTreeIcon(),
                new Scheduler.ScheduledCommand() {
                    @Override
                    public void execute() {
                        openDetailView();
                    }
                });
        contextMenu.addItem(mnuMethodTree);

        MenuItem mnuMethodRank = new MenuItem("Method call stats", Resources.INSTANCE.methodRankIcon(),
                new Scheduler.ScheduledCommand() {
                    @Override
                    public void execute() {
                        openRankingView();
                    }
                });
        contextMenu.addItem(mnuMethodRank);

        contextMenu.addSeparator();

        MenuItem mnuMethodAttrs = new MenuItem("Trace Attributes", Resources.INSTANCE.methodAttrsIcon(),
                new Scheduler.ScheduledCommand() {
                    @Override
                    public void execute() {
                        openMethodAttrsDialog();
                    }
                });
        contextMenu.addItem(mnuMethodAttrs);
    }

    private void intoSearchMode(boolean inSearch, final boolean moreResults, String message) {
        seqnum++;
        btnErrors.setEnabled(!inSearch);
        lstTraceType.setEnabled(!inSearch);
        txtDuration.setEnabled(!inSearch);
        btnEnableEql.setEnabled(!inSearch);
        txtFilter.setEnabled(!inSearch);
        btnRunSearch.setEnabled(!inSearch);
        btnClearFilters.setEnabled(!inSearch);
        btnFindMore.setEnabled(!inSearch && moreResults);

        if (inSearch) {
            md.info(MDS, message, "Cancel", new Scheduler.ScheduledCommand() {
                @Override
                public void execute() {
                    intoSearchMode(false, moreResults, "Search canceled.");
                }
            });
        } else if (moreResults) {
            md.info(MDS, message, "More", new Scheduler.ScheduledCommand() {
                @Override
                public void execute() {
                    loadMore(100);
                }
            });
        } else {
            md.info(MDS, message);
        }
    }

    private void openDetailView() {
        TraceInfo traceInfo = selection.getSelectedObject();
        if (traceInfo != null) {
            TraceCallTreePanel detail = pf.traceCallTreePanel(traceInfo);
            shell.get().addView(detail, ClientUtil.formatTimestamp(traceInfo.getClock()) + "@" + host.getName());
        }
    }

    private void openRankingView() {
        TraceInfo traceInfo = selection.getSelectedObject();
        if (traceInfo != null) {
            MethodRankingPanel ranking = pf.methodRankingPanel(traceInfo);
            shell.get().addView(ranking, ClientUtil.formatTimestamp(traceInfo.getClock()) + "@" + host.getName());
        }
    }

    private void openMethodAttrsDialog() {
        TraceInfo ti = selection.getSelectedObject();
        if (ti != null) {
            pf.methodAttrsDialog(ti.getHostName(), ti.getDataOffs(), "", 0L).asPopupWindow().show();
        }
    }

    public void refresh() {
        data.getList().clear();
        expandedDetails.clear();
        loadMore();
    }

    public void runSearch(String attrName, String filter, Date startDate, Date endDate) {
        txtFilter.setValue(filter, false);
        txtStartDate.setValue(ClientUtil.TSTAMP_FORMAT1.format(startDate), false);
        txtEndDate.setValue(ClientUtil.TSTAMP_FORMAT1.format(endDate), false);
        refresh();
    }

    @UiHandler("btnErrors")
    void toggleErrors(ClickEvent e) {
        refresh();
    }

    @UiHandler("btnFindMore")
    void findMoreClicked(ClickEvent e) {
        loadMore();
    }

    @UiHandler("btnStartDate")
    void setStartDate(ClickEvent e) {
        final DateTimePicker dtp = new DateTimePicker(true);

        if (txtStartDate.getText().length() > 0) {
            dtp.setValue(ClientUtil.parseDate(txtStartDate.getValue()));
        } else {
            Date dt = new Date();
            dt.setTime(dt.getTime() - 86400000L);
            dtp.setValue(dt);
        }

        dtp.setPopupPosition(Window.getClientWidth() - 384, e.getClientY());

        dtp.addValueChangeHandler(new ValueChangeHandler<Date>() {
            @Override
            public void onValueChange(ValueChangeEvent<Date> event) {
                txtStartDate.setValue(ClientUtil.TSTAMP_FORMAT1.format(event.getValue()));
                dtp.hide();
                refresh();
            }
        });

        dtp.show();
    }

    @UiHandler("btnEndDate")
    void setEndDate(ClickEvent e) {
        final DateTimePicker dtp = new DateTimePicker(true);

        if (txtEndDate.getText().length() > 0) {
            dtp.setValue(ClientUtil.parseDate(txtEndDate.getValue()));
        } else {
            Date dt = new Date();
            dtp.setValue(dt);
        }

        dtp.setPopupPosition(Window.getClientWidth() - 256, e.getClientY());

        dtp.addValueChangeHandler(new ValueChangeHandler<Date>() {
            @Override
            public void onValueChange(ValueChangeEvent<Date> event) {
                txtEndDate.setValue(ClientUtil.TSTAMP_FORMAT1.format(event.getValue()));
                dtp.hide();
                refresh();
            }
        });

        dtp.show();
    }

    private void loadMore() {
        loadMore(50);
    }

    private void loadMore(final int limit) {
        intoSearchMode(true, false, "Searching ...");
        TraceInfoSearchQuery q = new TraceInfoSearchQuery();
        q.setLimit(limit);
        q.setHostName(host.getName());
        q.setSeq(seqnum);

        q.setFlags(TraceInfoSearchQuery.ORDER_DESC | (btnErrors.isToggled() ? 0 : TraceInfoSearchQuery.ERRORS_ONLY)
                | (btnEnableEql.isToggled() ? TraceInfoSearchQuery.EQL_QUERY : 0));

        List<TraceInfo> list = data.getList();
        if (list.size() > 0) {
            q.setOffset(list.get(list.size() - 1).getDataOffs());
        }

        if (strTraceType != null && !"<all>".equals(strTraceType)) {
            q.setTraceName(strTraceType);
        }

        if (txtFilter.getText() != null && txtFilter.getText().length() > 0) {
            q.setSearchExpr(txtFilter.getText());
        }

        if (txtStartDate.getText() != null) {
            q.setStartDate(ClientUtil.parseTimestamp(txtStartDate.getText()));
        }

        if (txtEndDate.getText() != null) {
            q.setEndDate(ClientUtil.parseTimestamp(txtEndDate.getText()));
        }

        if (txtDuration.getText() != null && txtDuration.getText().length() > 0) {
            q.setMinMethodTime((Integer.parseInt(txtDuration.getText()) * 1000000000L));
        }

        md.info(MDS, "Searching for traces ...");
        traceDataService.search(q, new MethodCallback<TraceInfoSearchResult>() {
            @Override
            public void onFailure(Method method, Throwable e) {
                intoSearchMode(false, false, "Error occured while searching: " + e.getMessage());
                md.error(MDS, "Trace search request failed", e);
            }

            @Override
            public void onSuccess(Method method, TraceInfoSearchResult response) {
                if (response.getSeq() == seqnum) {
                    List<TraceInfo> results = response.getResults();
                    data.getList().addAll(results);
                    moreResults = 0 != (response.getFlags() & TraceInfoSearchResult.MORE_RESULTS);
                    intoSearchMode(false, moreResults, "Found " + data.getList().size() + " results.");
                    if (moreResults && results.size() < limit) {
                        loadMore(limit - results.size());
                    }
                    md.info(MDS, "Found: " + results.size() + " traces "
                            + (moreResults ? "(more to come - click 'Find More' button)." : "."));
                } else {
                    intoSearchMode(false, false, "No more records found.");
                }
            }
        });
    }

    private void loadTraceTypes() {
        systemService.getTidMap(host.getName(), new MethodCallback<List<SymbolInfo>>() {
            @Override
            public void onFailure(Method method, Throwable e) {
                md.error(MDS, "Error loading TID map", e);
            }

            @Override
            public void onSuccess(Method method, List<SymbolInfo> response) {
                lstTraceType.clear();
                lstTraceType.addItem("<all>");
                for (SymbolInfo e : response) {
                    lstTraceType.addItem(e.getName());
                }
            }
        });
    }

    private void toggleDetails(TraceInfo ti) {
        long offs = ti.getDataOffs();
        if (expandedDetails.contains(offs)) {
            expandedDetails.remove(offs);
        } else {
            expandedDetails.add(offs);
        }
        grid.redrawRow(data.getList().indexOf(ti));
    }

    private static final ProvidesKey<TraceInfo> KEY_PROVIDER = new ProvidesKey<TraceInfo>() {
        @Override
        public Object getKey(TraceInfo item) {
            return item.getDataOffs();
        }
    };

    private final static String SMALL_CELL_CSS = Resources.INSTANCE.zicoCssResources().traceSmallCell();
    private final static String SMALL_CELL_CSS_R = Resources.INSTANCE.zicoCssResources().traceSmallCellR();
    private static final String EXPANDER_EXPAND = AbstractImagePrototype.create(Resources.INSTANCE.expanderExpand())
            .getHTML();
    private static final String EXPANDER_COLLAPSE = AbstractImagePrototype
            .create(Resources.INSTANCE.expanderCollapse()).getHTML();

    private final Cell<TraceInfo> DETAIL_EXPANDER_CELL = new ActionCell<TraceInfo>("",
            new ActionCell.Delegate<TraceInfo>() {
                @Override
                public void execute(TraceInfo rec) {
                    toggleDetails(rec);
                }
            }) {
        @Override
        public void render(Cell.Context context, TraceInfo tr, SafeHtmlBuilder sb) {
            if ((tr.getAttributes() != null && tr.getAttributes().size() > 0) || tr.getExceptionInfo() != null) {
                sb.appendHtmlConstant("<span style=\"cursor: pointer;\">");
                sb.appendHtmlConstant(
                        expandedDetails.contains(tr.getDataOffs()) ? EXPANDER_COLLAPSE : EXPANDER_EXPAND);
                sb.appendHtmlConstant("</span>");
            }
        }
    };

    private AbstractCell<TraceInfo> TRACE_NAME_CELL = new AbstractCell<TraceInfo>() {
        @Override
        public void render(Context context, TraceInfo ti, SafeHtmlBuilder sb) {
            String color = ti.getStatus() != 0 ? "red" : "black";
            sb.appendHtmlConstant(
                    "<div class=\"" + SMALL_CELL_CSS + "\" style=\"color: " + color + "; text-align: left;\">");
            sb.append(SafeHtmlUtils.fromString(ti.getDescription()));
            sb.appendHtmlConstant("</div>");
        }
    };

    private AbstractCell<TraceInfo> TRACE_CLOCK_CELL = new AbstractCell<TraceInfo>() {
        @Override
        public void render(Context context, TraceInfo rec, SafeHtmlBuilder sb) {
            sb.appendHtmlConstant("<div class=\"" + SMALL_CELL_CSS + "\">");
            sb.append(SafeHtmlUtils.fromString(ClientUtil.formatTimestamp(rec.getClock())));
            sb.appendHtmlConstant("</div>");
        }
    };

    private AbstractCell<TraceInfo> TRACE_TYPE_CELL = new AbstractCell<TraceInfo>() {
        @Override
        public void render(Context context, TraceInfo rec, SafeHtmlBuilder sb) {
            sb.appendHtmlConstant("<div class=\"" + SMALL_CELL_CSS + "\">");
            sb.append(SafeHtmlUtils.fromString("" + rec.getTraceType()));
            sb.appendHtmlConstant("</div>");
        }
    };

    private AbstractCell<TraceInfo> TRACE_DURATION_CELL = new AbstractCell<TraceInfo>() {
        @Override
        public void render(Context context, TraceInfo rec, SafeHtmlBuilder sb) {
            sb.appendHtmlConstant("<div class=\"" + SMALL_CELL_CSS_R + "\">");
            sb.append(SafeHtmlUtils.fromString(ClientUtil.formatDuration(rec.getExecutionTime())));
            sb.appendHtmlConstant("</div>");
        }
    };

    private AbstractCell<TraceInfo> TRACE_CALLS_CELL = new AbstractCell<TraceInfo>() {
        @Override
        public void render(Context context, TraceInfo rec, SafeHtmlBuilder sb) {
            sb.appendHtmlConstant("<div class=\"" + SMALL_CELL_CSS_R + "\">");
            sb.append(SafeHtmlUtils.fromString("" + rec.getCalls()));
            sb.appendHtmlConstant("</div>");
        }
    };

    private AbstractCell<TraceInfo> TRACE_RECORDS_CELL = new AbstractCell<TraceInfo>() {
        @Override
        public void render(Context context, TraceInfo rec, SafeHtmlBuilder sb) {
            sb.appendHtmlConstant("<div class=\"" + SMALL_CELL_CSS_R + "\">");
            sb.append(SafeHtmlUtils.fromString("" + rec.getRecords()));
            sb.appendHtmlConstant("</div>");
        }
    };

    private AbstractCell<TraceInfo> TRACE_ERRORS_CELL = new AbstractCell<TraceInfo>() {
        @Override
        public void render(Context context, TraceInfo rec, SafeHtmlBuilder sb) {
            sb.appendHtmlConstant("<div class=\"" + SMALL_CELL_CSS_R + "\">");
            sb.append(SafeHtmlUtils.fromString("" + rec.getErrors()));
            sb.appendHtmlConstant("</div>");
        }
    };

}