Skip to content

Commit

Permalink
cmd, dashboard, log: log collection and exploration (ethereum#17097)
Browse files Browse the repository at this point in the history
* cmd, dashboard, internal, log, node: logging feature

* cmd, dashboard, internal, log: requested changes

* dashboard, vendor: gofmt, govendor, use vendored file watcher

* dashboard, log: gofmt -s -w, goimports

* dashboard, log: gosimple
  • Loading branch information
kurkomisi authored and karalabe committed Jul 11, 2018
1 parent 2eedbe7 commit a9835c1
Show file tree
Hide file tree
Showing 28 changed files with 11,214 additions and 7,981 deletions.
7 changes: 6 additions & 1 deletion cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,12 @@ func init() {

app.Before = func(ctx *cli.Context) error {
runtime.GOMAXPROCS(runtime.NumCPU())
if err := debug.Setup(ctx); err != nil {

logdir := ""
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
logdir = (&node.Config{DataDir: utils.MakeDataDir(ctx)}).ResolvePath("logs")
}
if err := debug.Setup(ctx, logdir); err != nil {
return err
}
// Cap the cache allowance and tune the garbage collector
Expand Down
2 changes: 1 addition & 1 deletion cmd/swarm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ pv(1) tool to get a progress bar:
app.Flags = append(app.Flags, swarmmetrics.Flags...)
app.Before = func(ctx *cli.Context) error {
runtime.GOMAXPROCS(runtime.NumCPU())
if err := debug.Setup(ctx); err != nil {
if err := debug.Setup(ctx, ""); err != nil {
return err
}
swarmmetrics.Setup(ctx)
Expand Down
4 changes: 2 additions & 2 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ var (
}
// Dashboard settings
DashboardEnabledFlag = cli.BoolFlag{
Name: "dashboard",
Name: metrics.DashboardEnabledFlag,
Usage: "Enable the dashboard",
}
DashboardAddrFlag = cli.StringFlag{
Expand Down Expand Up @@ -1185,7 +1185,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) {
// RegisterDashboardService adds a dashboard to the stack.
func RegisterDashboardService(stack *node.Node, cfg *dashboard.Config, commit string) {
stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
return dashboard.New(cfg, commit)
return dashboard.New(cfg, commit, ctx.ResolvePath("logs")), nil
})
}

Expand Down
17,652 changes: 9,898 additions & 7,754 deletions dashboard/assets.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dashboard/assets/common.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,4 @@ export const styles = {
light: {
color: 'rgba(255, 255, 255, 0.54)',
},
}
};
10 changes: 6 additions & 4 deletions dashboard/assets/components/Body.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ const styles = {
};

export type Props = {
opened: boolean,
opened: boolean,
changeContent: string => void,
active: string,
content: Content,
shouldUpdate: Object,
active: string,
content: Content,
shouldUpdate: Object,
send: string => void,
};

// Body renders the body of the dashboard.
Expand All @@ -52,6 +53,7 @@ class Body extends Component<Props> {
active={this.props.active}
content={this.props.content}
shouldUpdate={this.props.shouldUpdate}
send={this.props.send}
/>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion dashboard/assets/components/CustomTooltip.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export type Props = {
class CustomTooltip extends Component<Props> {
render() {
const {active, payload, tooltip} = this.props;
if (!active || typeof tooltip !== 'function') {
if (!active || typeof tooltip !== 'function' || !Array.isArray(payload) || payload.length < 1) {
return null;
}
return tooltip(payload[0].value);
Expand Down
45 changes: 31 additions & 14 deletions dashboard/assets/components/Dashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Header from './Header';
import Body from './Body';
import {MENU} from '../common';
import type {Content} from '../types/content';
import {inserter as logInserter} from './Logs';

// deepUpdate updates an object corresponding to the given update data, which has
// the shape of the same structure as the original object. updater also has the same
Expand Down Expand Up @@ -75,8 +76,11 @@ const appender = <T>(limit: number, mapper = replacer) => (update: Array<T>, pre
...update.map(sample => mapper(sample)),
].slice(-limit);

// defaultContent is the initial value of the state content.
const defaultContent: Content = {
// defaultContent returns the initial value of the state content. Needs to be a function in order to
// instantiate the object again, because it is used by the state, and isn't automatically cleaned
// when a new connection is established. The state is mutated during the update in order to avoid
// the execution of unnecessary operations (e.g. copy of the log array).
const defaultContent: () => Content = () => ({
general: {
version: null,
commit: null,
Expand All @@ -95,10 +99,14 @@ const defaultContent: Content = {
diskRead: [],
diskWrite: [],
},
logs: {
log: [],
logs: {
chunks: [],
endTop: false,
endBottom: true,
topChanged: 0,
bottomChanged: 0,
},
};
});

// updaters contains the state updater functions for each path of the state.
//
Expand All @@ -122,9 +130,7 @@ const updaters = {
diskRead: appender(200),
diskWrite: appender(200),
},
logs: {
log: appender(200),
},
logs: logInserter(5),
};

// styles contains the constant styles of the component.
Expand All @@ -151,10 +157,11 @@ export type Props = {
};

type State = {
active: string, // active menu
sideBar: boolean, // true if the sidebar is opened
content: Content, // the visualized data
shouldUpdate: Object, // labels for the components, which need to re-render based on the incoming message
active: string, // active menu
sideBar: boolean, // true if the sidebar is opened
content: Content, // the visualized data
shouldUpdate: Object, // labels for the components, which need to re-render based on the incoming message
server: ?WebSocket,
};

// Dashboard is the main component, which renders the whole page, makes connection with the server and
Expand All @@ -165,8 +172,9 @@ class Dashboard extends Component<Props, State> {
this.state = {
active: MENU.get('home').id,
sideBar: true,
content: defaultContent,
content: defaultContent(),
shouldUpdate: {},
server: null,
};
}

Expand All @@ -181,7 +189,7 @@ class Dashboard extends Component<Props, State> {
// PROD is defined by webpack.
const server = new WebSocket(`${((window.location.protocol === 'https:') ? 'wss://' : 'ws://')}${PROD ? window.location.host : 'localhost:8080'}/api`);
server.onopen = () => {
this.setState({content: defaultContent, shouldUpdate: {}});
this.setState({content: defaultContent(), shouldUpdate: {}, server});
};
server.onmessage = (event) => {
const msg: $Shape<Content> = JSON.parse(event.data);
Expand All @@ -192,10 +200,18 @@ class Dashboard extends Component<Props, State> {
this.update(msg);
};
server.onclose = () => {
this.setState({server: null});
setTimeout(this.reconnect, 3000);
};
};

// send sends a message to the server, which can be accessed only through this function for safety reasons.
send = (msg: string) => {
if (this.state.server != null) {
this.state.server.send(msg);
}
};

// update updates the content corresponding to the incoming message.
update = (msg: $Shape<Content>) => {
this.setState(prevState => ({
Expand Down Expand Up @@ -226,6 +242,7 @@ class Dashboard extends Component<Props, State> {
active={this.state.active}
content={this.state.content}
shouldUpdate={this.state.shouldUpdate}
send={this.send}
/>
</div>
);
Expand Down
Loading

0 comments on commit a9835c1

Please sign in to comment.