Skip to content

Commit

Permalink
Merge branch 'liveupdates'
Browse files Browse the repository at this point in the history
  • Loading branch information
pfirsich committed Jun 7, 2018
2 parents 61edf15 + 1fb190c commit 837f893
Show file tree
Hide file tree
Showing 9 changed files with 728 additions and 194 deletions.
74 changes: 58 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,39 +33,67 @@ Then calling `prof.write("myfile.prof")` will save a file to your applications s
# Documentation
Before you annotate your code, you need to copy (not move) `jprof.lua` and `MessagePack.lua` into your game's directory.

If you want to capture a profiling file, you need to set `PROF_CAPTURE` before you import jprof:
The most common case does probably look somewhat like this:
```lua
PROF_CAPTURE = true
prof = require "jprof"
prof = require("jprof")

function love.update(dt)
prof.push("frame")
-- push and pop additional zones here
-- also update your game if you want
end

function love.draw()
-- push and pop additional zones here
prof.pop("frame")
end
```

If `PROF_CAPTURE` evaluates to `false` when jprof is imported, all profiling functions are replaced with `function() end` i.e. do nothing, so you can leave them in even for release builds.

Also all other zones have to be pushed inside the `"frame"` zone and whenever `prof.push` or `prof.pop` are called outside of a frame, the viewer will not know how to interpret that data (and error). The idiomatic use is therefore something like this:
Also all other zones have to be pushed inside the `"frame"` zone and whenever `prof.push` or `prof.pop` are called outside of a frame, the viewer will not know how to interpret that data (and error). So make sure capturing is disabled when functions are called that push zones outside of the `"frame"` zone.

For example if you are using a fixed timestep loop (update and draw frames are not always 1 for 1), you probably want to do something like this instead (implementation of the fixed timestep loop not included):

```lua
PROFILE_DRAW = false

function love.update(dt)
prof.enabled(true)
prof.enabled(not PROFILE_DRAW)
prof.push("frame")
-- push and pop more here
-- also update your game if you want
-- updating
prof.pop("frame")
prof.enabled(false)
end
```
This makes sure that if functions that push profiling zones are used outside of `love.update`, the captures can still be interpreted by the viewer.

function love.draw()
prof.enabled(PROFILE_DRAW)
prof.push("frame")
-- drawing
prof.pop("frame")
prof.enabled(false)
end
```

### `prof.push(name, annotation)`
The annotation is optional and appears as metadata in the viewer.
The `annotation` is optional and appears as metadata in the viewer.

### `prof.pop(name)`
The name is optional and is only used to check if the current zone is actually the one specified as the argument. If not, somewhere before that pop-call another zone has been pushed, but not popped.
The `name` is optional and is only used to check if the current zone is actually the one specified as the argument. If not, somewhere before that pop-call another zone has been pushed, but not popped.

### `prof.write(filename)`
Writes the capture file
Writes the capture file to `filename`.

### `prof.enabled(enabled)`
Enables capturing profiling zones (`enabled = true`) or disables it (`enabled = false`)
Enables capturing profiling zones (`enabled = true`) or disables it (`enabled = false`). By default, profiling is enabled.

### `prof.connect(saveFullProfData, port, address)`
Attempts to connect to the jprof viewer to transmit realtime profiling data. If `saveFullProfData` is `true`, jprof will still save all the profiling data, so you can save it to file later using `prof.write()`. If it is `false` (default), the data is only transmitted to the viewer and `prof.write()` will show a notice that no profiling data was saved.
The default `port` is `1338` and the default `address` is `localhost`.

### `prof.netFlush()`
jprof does not send out every event by itself, but rather buffers them and sends them out, when this command is called. By default this is called when `prof.pop()` is called and the popped zone is `"frame"` (though only if you did `prof.connect()` earlier).

## Viewer
Just start the löve project contained in this repository like this:
Expand All @@ -74,13 +102,27 @@ love jprof <projectIdentity> <filename>
```
With `<projectIdentity>` being the [identity](https://love2d.org/wiki/love.filesystem.setIdentity) (most commonly set in [conf.lua](https://love2d.org/wiki/Config_Files)) of your project and `<filename>` being the filename of the capture file (the one that was passed to `prof.write(filename)`).

### Controls
You can seek frames with left click. If you hold shift while pressing left click the previously selected frame and the newly clicked frame will be averaged into a frame range, which is highly advised to find bottlenecks or get a general idea of memory development when you are not interested in a particular frame.
### Realtime Profiling
jprof also supports realtime transmission of profiling data. To use this feature, just start the viewer in listen mode:
```console
love jprof listen
```
You may also pass an additional, optional argument to specify a port. The default port used is 1338. In the program you are profiling, call `prof.connect()` (see above) right after importing jprof.

**Note:** When realtime profiling is used, it is not as straightforward to keep track of the memory jprof is using itself, since jprof will produce garbage too. Therefore the memory values returned by jprof will be less accurate and depending on your use case the garbage generated by jprof will dominate. Make sure to capture to file first and see if the live capture looks significantly different.

### Notes
Hold `F1` or `H` to show the help overlay.

If a single frame is selected, you can additionally navigate using the left and right arrow key and skip 100 instead of 1 frame, if you also hold ctrl.
When you select a frame range, it will be averaged. Most of the time this is what you want to look at rather than individual frames.

If a single frame is selected the position of the zones in the flame graph will correspond to their relative position in time inside the frame, for averaged frames both in memory and time mode the zones will just be centered above their parent. Their size will still hold meaning though and empty space surrounding these zones implies that there was memory consumed/freed or time spent without being enclosed by a profiling zone.

With the space key you can switch between memory and time mode, which will scale and position the zones inside the flame graph according to memory memory consumption changes or time duration respectively.
The different modes (`memory` and `time`) determine whether the scale and position of the zones inside the flame graph will be derived from either memory consumption changes or time duration respectively.

The purple graph displays the total duration of the frames over time and the green graph the total memory consumption over time.

### Graph Averaging Modes
* `max` mean is most useful for finding spikes. This is the default.
* `arithmetic` mean is what most people think of, when they think of an average. This is less sensitive to spikes, but still somewhat.
* `harmonic` mean is least sensitive to outliers and should be a bit smoother than the arithmetic mean.
9 changes: 9 additions & 0 deletions const.lua
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
local const = {
defaultPort = 1338,

frameOverviewHeight = 40,
graphHeightFactor = 0.3,
infoLineHeight = 35,
nodeHeight = 40,

noticeDuration = 4,
noticeFadeoutAfter = 3,

-- colors
textColor = {1, 1, 1},

Expand All @@ -18,6 +23,10 @@ local const = {
graphBorderColor = {0.3, 0.3, 0.3},
timeGraphColor = {1, 0, 1},
memGraphColor = {0, 1, 0},

helpOverlayColor = {0, 0, 0, 0.8},
helpTitleColor = {1, 1, 1},
helpColor = {0.85, 0.85, 0.85},
}

const.graphYOffset = const.frameOverviewHeight + 20
Expand Down
Loading

0 comments on commit 837f893

Please sign in to comment.