Skip to content

Commit

Permalink
Add a tooltip for samples in the thread activity graph
Browse files Browse the repository at this point in the history
  • Loading branch information
gregtatum committed Jan 8, 2019
1 parent 32558fb commit 476b503
Show file tree
Hide file tree
Showing 16 changed files with 274 additions and 125 deletions.
5 changes: 0 additions & 5 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ module.exports = {
// See https://github.com/yannickcr/eslint-plugin-react/issues/1561
// 'react/no-unused-prop-types': 'error',
'react/no-unused-state': 'error',
'react/prefer-stateless-function': 'error',
'react/prefer-stateless-function': [
'error',
{ ignorePureComponents: true },
],
'react/jsx-no-bind': 'error',
'flowtype/require-valid-file-annotation': [ 'error', 'always', { annotationStyle: 'line' } ],
// no-dupe-keys crashes with recent eslint. See
Expand Down
52 changes: 52 additions & 0 deletions res/css/categories.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
* This class should be used to create a small color swatch to describe a
* category. It is a global css class as it can be used across the project.
*/
.category-swatch {
display: inline-block;
box-sizing: border-box;
width: 9px;
height: 9px;
border: 0.5px solid rgba(0, 0, 0, 0.1);
margin-right: 3px;
}

.category-swatch.category-color-transparent {
background-color: transparent;
}

.category-swatch.category-color-purple {
background-color: var(--purple-70);
}

.category-swatch.category-color-green {
background-color: var(--green-60);
}

.category-swatch.category-color-orange {
background-color: var(--orange-50);
}

.category-swatch.category-color-yellow {
background-color: var(--yellow-50);
}

.category-swatch.category-color-lightblue {
background-color: var(--blue-40);
}

.category-swatch.category-color-grey {
background-color: var(--grey-30);
}

.category-swatch.category-color-blue {
background-color: var(--blue-60);
}

.category-swatch.category-color-brown {
background-color: var(--magenta-60);
}
36 changes: 0 additions & 36 deletions res/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -197,42 +197,6 @@ body,
color: highlighttext;
}

.treeViewCategoryKnob {
display: inline-block;
box-sizing: border-box;
width: 9px;
height: 9px;
border: 0.5px solid rgba(0, 0, 0, 0.1);
margin-right: 3px;
}
.treeViewCategoryKnob.category-color-transparent {
background-color: transparent;
}
.treeViewCategoryKnob.category-color-purple {
background-color: var(--purple-70);
}
.treeViewCategoryKnob.category-color-green {
background-color: var(--green-60);
}
.treeViewCategoryKnob.category-color-orange {
background-color: var(--orange-50);
}
.treeViewCategoryKnob.category-color-yellow {
background-color: var(--yellow-50);
}
.treeViewCategoryKnob.category-color-lightblue {
background-color: var(--blue-40);
}
.treeViewCategoryKnob.category-color-grey {
background-color: var(--grey-30);
}
.treeViewCategoryKnob.category-color-blue {
background-color: var(--blue-60);
}
.treeViewCategoryKnob.category-color-brown {
background-color: var(--magenta-60);
}

.treeViewHighlighting {
/* This negative margin enlarges the background to the top, so that it fully
* covers the underlying background. There's an underlying background when the
Expand Down
1 change: 0 additions & 1 deletion src/components/shared/Backtrace.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
.backtrace {
--max-height: 30em;
--gradient-height: calc(var(--max-height) - 5em);

margin: 5px;
Expand Down
12 changes: 6 additions & 6 deletions src/components/shared/Backtrace.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,29 @@ import {
convertStackToCallNodePath,
} from '../../profile-logic/profile-data';

import type { Thread } from '../../types/profile';
import type { CauseBacktrace } from '../../types/markers';
import type { Thread, IndexIntoStackTable } from '../../types/profile';
import type { ImplementationFilter } from '../../types/actions';

require('./Backtrace.css');

type Props = {|
+thread: Thread,
+cause: CauseBacktrace,
+maxHeight: string | number,
+stackIndex: IndexIntoStackTable,
+implementationFilter: ImplementationFilter,
|};

function Backtrace(props: Props) {
const { cause, thread, implementationFilter } = props;
const { stackIndex, thread, implementationFilter, maxHeight } = props;
const { funcTable, stringTable } = thread;
const callNodePath = filterCallNodePathByImplementation(
thread,
implementationFilter,
convertStackToCallNodePath(thread, cause.stack)
convertStackToCallNodePath(thread, stackIndex)
);

return (
<ol className="backtrace">
<ol className="backtrace" style={{ '--max-height': maxHeight }}>
{callNodePath.length > 0 ? (
callNodePath.map((func, i) => (
<li key={i} className="backtraceStackFrame">
Expand Down
3 changes: 2 additions & 1 deletion src/components/shared/MarkerTooltipContents.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,8 @@ function _markerBacktrace(
First invalidated {formatNumber(causeAge)}ms before the flush, at:
</h2>
<Backtrace
cause={cause}
maxHeight="30em"
stackIndex={cause.stack}
thread={thread}
implementationFilter={implementationFilter}
/>
Expand Down
58 changes: 58 additions & 0 deletions src/components/shared/SampleTooltipContents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// @flow

import * as React from 'react';
import type {
IndexIntoSamplesTable,
CategoryList,
Thread,
} from '../../types/profile';
import Backtrace from './Backtrace';

type Props = {|
+sampleIndex: IndexIntoSamplesTable,
+categories: CategoryList,
+fullThread: Thread,
|};

/**
* This class displays the tooltip contents for a given sample. Typically the user
* will want to know what the function is, and its category.
*/
export default class SampleTooltipContents extends React.PureComponent<Props> {
render() {
const { sampleIndex, fullThread, categories } = this.props;
const { samples, stackTable } = fullThread;
const stackIndex = samples.stack[sampleIndex];
if (stackIndex === null) {
return 'No stack information';
}
const categoryIndex = stackTable.category[stackIndex];
const category = categories[categoryIndex];

return (
<>
<div className="tooltipDetails">
<div className="tooltipLabel">Category:</div>
<div>
<span
className={`category-swatch category-color-${category.color}`}
/>
{category.name}
</div>
</div>
<div className="tooltipDetails">
<div className="tooltipLabel">Stack:</div>
</div>
<Backtrace
maxHeight="9.2em"
stackIndex={stackIndex}
thread={fullThread}
implementationFilter="combined"
/>
</>
);
}
}
2 changes: 1 addition & 1 deletion src/components/shared/TreeView.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ class TreeViewRowScrolledColumns<
>
{displayData.categoryColor && displayData.categoryName ? (
<span
className={`treeViewCategoryKnob category-color-${
className={`category-swatch category-color-${
displayData.categoryColor
}`}
title={displayData.categoryName}
Expand Down
71 changes: 62 additions & 9 deletions src/components/shared/thread/ActivityGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// @flow

import React, { PureComponent } from 'react';
import * as React from 'react';
import { computeActivityGraphFills } from './ActivityGraphFills';
import { timeCode } from '../../../utils/time-code';
import classNames from 'classnames';
import photonColors from 'photon-colors';
import Tooltip, { MOUSE_OFFSET } from '../Tooltip';
import SampleTooltipContents from '../SampleTooltipContents';

import './ActivityGraph.css';

Expand All @@ -16,7 +18,7 @@ import type {
CategoryList,
IndexIntoSamplesTable,
} from '../../../types/profile';
import type { Milliseconds } from '../../../types/units';
import type { Milliseconds, CssPixels } from '../../../types/units';
import type {
CategoryDrawStyles,
ActivityFillGraphQuerier,
Expand All @@ -37,12 +39,42 @@ export type Props = {|
) => number,
|};

class ThreadActivityGraph extends PureComponent<Props> {
type State = {
hoveredSample: null | IndexIntoSamplesTable,
mouseX: CssPixels,
mouseY: CssPixels,
};

class ThreadActivityGraph extends React.PureComponent<Props, State> {
_canvas: null | HTMLCanvasElement = null;
_resizeListener = () => this.forceUpdate();
_categoryDrawStyles: null | CategoryDrawStyles = null;
_fillsQuerier: null | ActivityFillGraphQuerier = null;

state = {
hoveredSample: null,
mouseX: 0,
mouseY: 0,
};

_onMouseLeave = () => {
this.setState({ hoveredSample: null });
};

_onMouseMove = (event: SyntheticMouseEvent<HTMLDivElement>) => {
const canvas = this._canvas;
if (!canvas) {
return;
}
const rect = canvas.getBoundingClientRect();
this.setState({
hoveredSample: this._getSampleAtMouseEvent(event),
mouseX: event.pageX,
// Have the tooltip align to the bottom of the track.
mouseY: rect.bottom - MOUSE_OFFSET,
});
};

_takeCanvasRef = (canvas: HTMLCanvasElement | null) => {
this._canvas = canvas;
};
Expand Down Expand Up @@ -186,30 +218,42 @@ class ThreadActivityGraph extends PureComponent<Props> {
}
}

_onMouseUp = (e: SyntheticMouseEvent<>) => {
_getSampleAtMouseEvent(
event: SyntheticMouseEvent<>
): null | IndexIntoSamplesTable {
// Create local variables so that Flow can refine the following to be non-null.
const fillsQuerier = this._fillsQuerier;
const canvas = this._canvas;
if (!canvas || !fillsQuerier) {
return;
return null;
}
// Re-measure the canvas and get the coordinates and time for the click.
const { rangeStart, rangeEnd } = this.props;
const rect = canvas.getBoundingClientRect();
const x = e.pageX - rect.left;
const y = e.pageY - rect.top;
const x = event.pageX - rect.left;
const y = event.pageY - rect.top;
const time = rangeStart + x / rect.width * (rangeEnd - rangeStart);

const sample = fillsQuerier.getSampleAtClick(x, y, time, rect);
return fillsQuerier.getSampleAtClick(x, y, time, rect);
}

_onMouseUp = (event: SyntheticMouseEvent<>) => {
const sample = this._getSampleAtMouseEvent(event);
if (sample !== null) {
this.props.onSampleClick(sample);
}
};

render() {
this._renderCanvas();
const { fullThread, categories } = this.props;
const { hoveredSample, mouseX, mouseY } = this.state;
return (
<div className={this.props.className}>
<div
className={this.props.className}
onMouseMove={this._onMouseMove}
onMouseLeave={this._onMouseLeave}
>
<canvas
className={classNames(
`${this.props.className}Canvas`,
Expand All @@ -218,6 +262,15 @@ class ThreadActivityGraph extends PureComponent<Props> {
ref={this._takeCanvasRef}
onMouseUp={this._onMouseUp}
/>
{hoveredSample === null ? null : (
<Tooltip mouseX={mouseX} mouseY={mouseY}>
<SampleTooltipContents
sampleIndex={hoveredSample}
fullThread={fullThread}
categories={categories}
/>
</Tooltip>
)}
</div>
);
}
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Root from './components/app/Root';
import createStore from './app-logic/create-store';
import 'photon-colors/photon-colors.css';
import '../res/css/style.css';
import '../res/css/categories.css';
import {
addDataToWindowObject,
logFriendlyPreamble,
Expand Down
6 changes: 6 additions & 0 deletions src/test/components/__snapshots__/GlobalTrack.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ process: \\"tab\\" (222)"
</div>
<div
className="threadActivityGraph"
onMouseLeave={[Function]}
onMouseMove={[Function]}
>
<canvas
className="threadActivityGraphCanvas threadActivityGraphCanvas"
Expand Down Expand Up @@ -119,6 +121,8 @@ process: \\"tab\\" (222)"
</div>
<div
className="threadActivityGraph"
onMouseLeave={[Function]}
onMouseMove={[Function]}
>
<canvas
className="threadActivityGraphCanvas threadActivityGraphCanvas"
Expand Down Expand Up @@ -176,6 +180,8 @@ process: \\"tab\\" (222)"
</div>
<div
className="threadActivityGraph"
onMouseLeave={[Function]}
onMouseMove={[Function]}
>
<canvas
className="threadActivityGraphCanvas threadActivityGraphCanvas"
Expand Down
Loading

0 comments on commit 476b503

Please sign in to comment.