Skip to content

Commit

Permalink
feat: add report filtering config options (#11)
Browse files Browse the repository at this point in the history
* feat: Customisation option to toggle report sections

Show/hide report section (branch, project, file, filetypes, directories)

* Refactor to consolidate report config options into one

this commit addresses code review comments and suggestions

* Set default configuration options for report_excludes

default: show all

* feat: Introduce config option to filter data for each report section

* doc: Updated documentation

* doc: Update README.md

* refactor: remove print from setup

---------

Co-authored-by: Patrick Dewey <57921252+ptdewey@users.noreply.github.com>
  • Loading branch information
csessh and ptdewey authored Oct 20, 2024
1 parent c5adcd4 commit 3bc65c8
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 40 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,4 @@ luac.out

## Extras
remote/pendulum-nvim
.luarc.json
53 changes: 44 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Install Pendulum using your favorite package manager:
#### With Report Generation (Requires Go)

With lazy.nvim

```lua
{
"ptdewey/pendulum-nvim",
Expand All @@ -37,6 +38,7 @@ With lazy.nvim
#### Without Report Generation

With lazy.nvim

```lua
{
"ptdewey/pendulum-nvim",
Expand All @@ -52,13 +54,15 @@ With lazy.nvim

Pendulum can be customized with several options. Here is a table with configurable options:

| Option | Description | Default |
|---------------|---------------------------------------------------|--------------------------------|
| `log_file` | Path to the CSV file where logs should be written | `$HOME/pendulum-log.csv` |
| `timeout_len` | Length of time in seconds to determine inactivity | `180` |
| `timer_len` | Interval in seconds at which to check activity | `120` |
| `gen_reports` | Generate reports from the log file | `true` |
| `top_n` | Number of top entries to include in the report | `5` |
| Option | Description | Default |
|-----------------------------|---------------------------------------------------------|--------------------------|
| `log_file` | Path to the CSV file where logs should be written | `$HOME/pendulum-log.csv` |
| `timeout_len` | Length of time in seconds to determine inactivity | `180` |
| `timer_len` | Interval in seconds at which to check activity | `120` |
| `gen_reports` | Generate reports from the log file | `true` |
| `top_n` | Number of top entries to include in the report | `5` |
| `report_section_excludes` | Additional filters to be applied to each report section | `{}` |
| `report_excludes` | Show/Hide report sections. e.g `branch`, `directory`, `file`, `filetype`, `project` | `{}` |

Example configuration with custom options:

Expand All @@ -69,9 +73,40 @@ require('pendulum').setup({
timer_len = 60, -- 1 minute
gen_reports = true, -- Enable report generation (requires Go)
top_n = 10, -- Include top 10 entries in the report
report_section_excludes = {
"branch", -- Hide `branch` section of the report
-- Other options includes:
-- "directory",
-- "filetype",
-- "file",
-- "project",
},
report_excludes = {
filetype = {
-- This table controls what to be excluded from `filetype` section
"neo-tree", -- Exclude neo-tree filetype
},
file = {
-- This table controls what to be excluded from `file` section
"test.py", -- Exclude any test.py
".*.go", -- Exclude all Go files
}
project = {
-- This table controls what to be excluded from `project` section
"unknown_project" -- Exclude unknown (non-git) projects
},
directory = {
-- This table controls what to be excluded from `directory` section
},
branch = {
-- This table controls what to be excluded from `branch` section
},
},
})
```

**Note**: You can use regex to express the matching patterns within `report_excludes`.

## Usage

Once configured, Pendulum runs automatically in the background. It logs each specified event into the CSV file, which includes timestamps, file names, project names (from Git), and activity states.
Expand All @@ -92,7 +127,6 @@ To rebuild the Pendulum binary and generate reports, use the following commands:
The :PendulumRebuild command recompiles the Go binary, and the :Pendulum command generates the report based on the current log file.
I recommend rebuilding the binary after the plugin is updated.


If you do not want to install Go, report generation can be disabled by changing the `gen_reports` option to `false`. Disabling reports will cause the `Pendulum` and `PendulumRebuild` commands to not be created since they are exclusively used for the reports feature.

```lua
Expand All @@ -104,6 +138,8 @@ config = function()
end,
```

The report contents are customizable and section items or entire sections can be excluded from the report if desired. (See `report_excludes` and `report_section_excludes` options in setup)

## Future Ideas

These are some potential future ideas that would make for welcome contributions for anyone interested.
Expand All @@ -113,4 +149,3 @@ These are some potential future ideas that would make for welcome contributions
- Get stats for specified project, filetype, etc. (Could work well with Telescope)
- Nicer looking popup with custom highlight groups
- Alternative version of popup that uses a terminal buffer and [bubbletea](https://github.com/charmbracelet/bubbletea) (using the table component)

8 changes: 8 additions & 0 deletions lua/pendulum/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ local default_opts = {
timer_len = 120,
gen_reports = true,
top_n = 5,
report_excludes = {
branch = {},
directory = {},
file = {},
filetype = {},
project = {},
},
report_section_excludes = {},
}

---set up plugin autocommands with user options
Expand Down
5 changes: 5 additions & 0 deletions lua/pendulum/remote.lua
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ local function setup_pendulum_commands()
timer_len = options.timer_len,
top_n = options.top_n,
time_range = time_range,
report_excludes = options.report_excludes,
report_section_excludes = options.report_section_excludes,
}

local success, result =
Expand Down Expand Up @@ -96,6 +98,8 @@ function M.setup(opts)
options.log_file = opts.log_file
options.timer_len = opts.timer_len
options.top_n = opts.top_n or 5
options.report_excludes = opts.report_excludes
options.report_section_excludes = opts.report_section_excludes

-- get plugin install path
plugin_path = debug.getinfo(1).source:sub(2):match("(.*/).*/.*/")
Expand Down Expand Up @@ -127,6 +131,7 @@ function M.setup(opts)
.. bin_path
.. ", attempting to compile with Go..."
)

local result =
os.execute("cd " .. plugin_path .. "remote" .. " && go build")
if result == 0 then
Expand Down
114 changes: 97 additions & 17 deletions remote/internal/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package internal

import (
"log"
"regexp"
"strconv"
"sync"
"time"
Expand All @@ -24,47 +25,88 @@ type PendulumEntry struct {
ActivePct float32
}

var csvColumns = map[string]int{
"active": 0,
"branch": 1,
"directory": 2,
"file": 3,
"filetype": 4,
"project": 5,
"time": 6,
}

// AggregatePendulumMetrics processes the input data to compute metrics for each column.
//
// Parameters:
// - data: A 2D slice of strings representing the pendulum data.
// - timeout_len: A float64 representing the timeout length.
// - rangeType: A string representing the time window to aggregate data for ("all" is recommended)
// - reportSectionExcludes: An array of report section names. e.g. "branch", "files"...
// - reportExcludes: A map of filters.
//
// Returns:
// - A slice of PendulumMetric structs containing the aggregated metrics.
func AggregatePendulumMetrics(data [][]string, timeout_len float64, rangeType string) []PendulumMetric {
// get number of columns
n := len(data[0])

func AggregatePendulumMetrics(
data [][]string,
timeout_len float64,
rangeType string,
reportSectionExcludes []interface{},
reportExcludes map[string]interface{},
) []PendulumMetric {
// create waitgroup
var wg sync.WaitGroup

// create buffered channel to store results and avoid deadlock in main
res := make(chan PendulumMetric, n-2)
res := make(chan PendulumMetric, len(data[0]))

// iterate through each metric column as specified in Sections config
// and create goroutine for each
for col := range len(csvColumns) {
if col == csvColumns["active"] || col == csvColumns["time"] {
continue
}

isExcluded := false
for _, section := range reportSectionExcludes {
if col == csvColumns[section.(string)] {
isExcluded = true
break
}
}

if isExcluded {
continue
}

// iterate through each metric column (except for 'active' and 'time') and create goroutine for each
for m := 1; m < n-1; m++ {
wg.Add(1)
go func(m int) {
defer wg.Done()
aggregatePendulumMetric(data, m, timeout_len, rangeType, res)
}(m)
aggregatePendulumMetric(
data,
m,
timeout_len,
rangeType,
reportExcludes,
res,
)
}(col)
}

// handle waitgroup in separate goroutine to allow main routine to process results as they become available.
// handle waitgroup in separate goroutine to allow main routine
// to process results as they become available.
var cleanup_wg sync.WaitGroup
cleanup_wg.Add(1)

go func() {
wg.Wait()
close(res)
cleanup_wg.Done()
}()

// deal with results
out := make([]PendulumMetric, n-2)
out := make([]PendulumMetric, len(data[0]))
for r := range res {
out[r.Index-1] = r
out[r.Index] = r
}

// wait for cleanup goroutine to finish
Expand All @@ -82,15 +124,29 @@ func AggregatePendulumMetrics(data [][]string, timeout_len float64, rangeType st
// - rangeType: A string representing the time window to aggregate data for ("all" is recommended)
// - ch: A channel to send the aggregated PendulumMetric.
//
// Returns:
// - None
func aggregatePendulumMetric(data [][]string, m int, timeout_len float64, rangeType string, ch chan<- PendulumMetric) {
// // Returns:
// // - None
func aggregatePendulumMetric(
data [][]string,
m int,
timeout_len float64,
rangeType string,
reportExcludes map[string]interface{},
ch chan<- PendulumMetric,
) {
out := PendulumMetric{
Name: data[0][m],
Index: m,
Value: make(map[string]*PendulumEntry),
}
timecol := len(data[0]) - 1

timecol := csvColumns["time"]
colName := out.Name
if colName == "cwd" {
// This is a bit hacky. csv uses cwd, code uses directory.
// TODO: consolidate these two terms?
colName = "directory"
}

// iterate through each row of data
for i := 1; i < len(data[:]); i++ {
Expand All @@ -106,12 +162,34 @@ func aggregatePendulumMetric(data [][]string, m int, timeout_len float64, rangeT
if err != nil {
return
}

if !inRange {
continue
}

// check if key doesn't exist in value map
val := data[i][m]

isExcluded := false
for _, expr := range reportExcludes[colName].([]interface{}) {
r, err := regexp.Compile(expr.(string))
if err != nil {
log.Printf("Error parsing regex: %s for %s", expr, colName)
continue
}

match := r.MatchString(val)

if match {
isExcluded = true
break
}
}

if isExcluded {
continue
}

// check if key doesn't exist in value map
if out.Value[val] == nil {
out.Value[val] = &PendulumEntry{
ID: val,
Expand All @@ -132,6 +210,7 @@ func aggregatePendulumMetric(data [][]string, m int, timeout_len float64, rangeT
if err != nil {
return
}

pv.TotalTime += t

// active-only metrics aggregation
Expand All @@ -142,6 +221,7 @@ func aggregatePendulumMetric(data [][]string, m int, timeout_len float64, rangeT
if err != nil {
return
}

pv.ActiveTime += t
}
}
Expand Down
4 changes: 3 additions & 1 deletion remote/internal/prettify.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ func PrettifyMetrics(metrics []PendulumMetric, n int) []string {
// iterate over each metric
for _, metric := range metrics {
// TODO: redefine order? (might require hardcoding)
lines = append(lines, prettifyMetric(metric, n))
if metric.Name != "" && len(metric.Value) != 0 {
lines = append(lines, prettifyMetric(metric, n))
}
}

return lines
Expand Down
15 changes: 10 additions & 5 deletions remote/pkg/buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func CreateBuffer(v *nvim.Nvim, args PendulumArgs) (nvim.Buffer, error) {
}

// get prettified buffer text
bufText := getBufText(data, args.Timeout, args.TopN, args.TimeRange)
bufText := getBufText(data, args)

// set contents of new buffer
if err := v.SetBufferLines(buf, 0, -1, false, bufText); err != nil {
Expand Down Expand Up @@ -55,10 +55,15 @@ func CreateBuffer(v *nvim.Nvim, args PendulumArgs) (nvim.Buffer, error) {
//
// Returns:
// - A 2D slice of bytes representing the text to be set in the buffer.
func getBufText(data [][]string, timeoutLen float64, n int, rangeType string) [][]byte {
out := internal.AggregatePendulumMetrics(data, timeoutLen, rangeType)

lines := internal.PrettifyMetrics(out, n)
func getBufText(data [][]string, args PendulumArgs) [][]byte {
out := internal.AggregatePendulumMetrics(
data[:],
args.Timeout,
args.TimeRange,
args.ReportSectionExcludes,
args.ReportExcludes,
)
lines := internal.PrettifyMetrics(out, args.TopN)

var bufText [][]byte
for _, l := range lines {
Expand Down
Loading

0 comments on commit 3bc65c8

Please sign in to comment.