Skip to content

Commit

Permalink
enable report, add tests, split types and tracker
Browse files Browse the repository at this point in the history
  • Loading branch information
tillkuhn committed Oct 1, 2024
1 parent 7bdf50c commit 1687516
Show file tree
Hide file tree
Showing 17 changed files with 429 additions and 193 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Publish to ghcr.io
name: Release Go Binaries

on:
push:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,4 @@ fabric.properties

.env
dist/
coverage.html
52 changes: 29 additions & 23 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ help: ## Shows the help

# default goreleaser dist/billy-idle_darwin_arm64/billy
.PHONY: build-mac
build-mac: ## build for mac current arch
build-mac: ## build for mac current arch using default goreleaser target path
GOARCH=$(ARCH) CGO_ENABLED=0 GOOS=darwin go build -v --ldflags="$(LDFLAGS)" \
-o dist/$(APP_NAME)_$(OS)_$(ARCH)/$(BINARY)

Expand All @@ -56,29 +56,18 @@ release: ## run goreleaser in snapshot mode
clean: ## Clean output directory
rm -rf dist/

.PHONY: run
run: ## Run app in tracker mode
go run main.go track -env dev -idle 10s -interval 5s -debug -drop-create

.PHONY: run-help
run-help: ## Run app in help mode
@go run main.go help
lint: ## Lint go code
@go fmt ./...
@golangci-lint run --fix

.PHONY: test
test: ## Runs all tests (with colorized output support if gotest is installed)
test: lint ## Run tests with coverage, implies lint
@if hash gotest 2>/dev/null; then \
gotest -v -coverpkg=./... -coverprofile=coverage.out ./...; \
else go test -v -coverpkg=./... -coverprofile=coverage.out ./...; fi

#@go tool cover -func coverage.out | grep "total:"
.PHONY: coverage
coverage: test ## Displays coverage per func on cli
go tool cover -func=coverage.out

.PHONY: lint
lint: ## Lint go code
@go fmt ./...
@golangci-lint run --fix
else go test -v -coverpkg=./... -coverprofile=coverage.out ./...; fi
@go tool cover -func coverage.out | grep "total:"
go tool cover -html=coverage.out -o coverage.html
@echo For coverage report open coverage.html

.PHONY: tidy
tidy: ## Add missing and remove unused modules
Expand All @@ -92,25 +81,42 @@ update: ## Update all go dependencies
# Custom Targets
#-------------------

.PHONY: run
run: ## Run app in tracker mode, add -drop-create to recreate db
go run main.go track -env dev -idle 10s -interval 5s -debug

.PHONY: report-dev
report-dev: ## Show report for dev db
go run main.go report -env dev -debug

.PHONY: report
report: ## Show report for default db
go run main.go report -debug


.PHONY: run-help
run-help: ## Run app in help mode
@go run main.go help

.PHONY: install
install: build-mac ## Install as launchd managed service
@mkdir -p $(HOME)/.billy-idle
@if launchctl list $(LAUNCHD_LABEL) 2>/dev/null|grep '"Program"'; then \
echo "$(LAUNCHD_LABEL) is loaded, trigger unload"; \
launchctl unload -w ~/Library/LaunchAgents/$(LAUNCHD_LABEL).plist; \
fi
cp bin/darwin/$(ARCH)/$(BINARY) $(HOME)/bin/$(BINARY)
cp dist/$(APP_NAME)_$(OS)_$(ARCH)/$(BINARY) $(HOME)/bin/$(BINARY)
cat agent.plist |envsubst '$$HOME' > $(HOME)/Library/LaunchAgents/$(LAUNCHD_LABEL).plist
launchctl load -w ~/Library/LaunchAgents/$(LAUNCHD_LABEL).plist
launchctl list $(LAUNCHD_LABEL) | grep '"PID"'
@sleep 1
@ps -ef |grep -v grep |grep $(HOME)/bin/billy
@tail $(HOME)/.billy-idle/agent.log
@tail $(HOME)/.billy-idle/default/agent.log


.PHONY: logs
logs: ## Show agent logs
@tail -120 $(HOME)/.billy-idle/agent.log
@tail -120 $(HOME)/.billy-idle/default/agent.log


.PHONY: minor
Expand Down
64 changes: 47 additions & 17 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,41 @@ According to the article, OS X has a timer called *HIDIdleTime* that tracks the

*billy-idle* simply queries this value periodically using the `ioreg` utility that ships with macOS, and matches it against a pre-defined threshold. If exceeded, it will create a record for the busy time period in database. This data can later be used as input for time tracking tools or statistics.

== Example Report

----
------------------------------------ DAILY BILLY IDLE REPORT ------------------------------
Tuesday 13:10:03: Spent 2m30s Drinking a Bock Celebrator Doppelbock
Tuesday 13:54:12: Spent 40m52s Building app Horsehad in Cayenne
Tuesday 14:07:48: Spent 2s Driving a Highlander Hybrid 2wd Passenger car compact to Austin
Tuesday 14:10:23: Spent 1s Drinking a Bock HopSlam Ale
Tuesday 14:10:35: Spent 2s Driving a Sl600 Passenger car light to Winston-Salem
Tuesday 14:11:21: Spent 5s Drinking a Porter Racer 5 India Pale Ale, Bear Republic Bre
Tuesday 14:11:32: Spent 3s Drinking a Belgian And French Ale Pliny The Elder
Tuesday 14:12:48: Spent 1s Drinking a Dark Lager Founders Breakfast Stout
Tuesday 14:35:04: Spent 1m6s Building app Thingdoes in SASL
Tuesday 14:36:10: Spent 23s Drinking a Porter Orval Trappist Ale
Tuesday 14:36:33: Spent 1h51m16s Eating a Viskos praline sauce with Blueberry
Tuesday 16:28:49: Spent 44m40s Driving a Charger Sport utility vehicle to St. Petersburg
------------------------------------------------------------------------------------
Total time spent: 3h21m0s (net) / 3h51m0s (with 30m0s kitKat break)
------------------------------------------------------------------------------------
----

== Run form source

[source,shell]
----
$ go run main.go
2024/09/28 00:43:52 [BigMac] 🎬 billy-idle tracker started version=latest commit=e95d72f
2024/09/28 00:44:09 [BigMac] 💤 Entering idle mode after 17s of busy time, completing record #1
2024/09/28 00:44:13 [BigMac] 🐝 Resuming busy mode after 4s of idle time, creating new record #2
2024/09/28 00:44:27 [BigMac] 💤 Entering idle mode after 14s of busy time, completing record #2
2024/09/28 00:45:25 [BigMac] 🐝 Resuming busy mode after 58s of idle time, creating new record #3
2024/09/28 00:45:37 [BigMac] 💤 Entering idle mode after 12s of busy time, completing record #3
2024/09/28 00:45:45 [BigMac] 🐝 Resuming busy mode after 8s of idle time, creating new record #4
2024/09/28 00:45:48 [BigMac] 🛑 Received Signal interrupt
2024/09/28 00:45:49 [BigMac] 🛑 tracker stopped
2024/10/01 17:13:29 🎬 billy started version=v0.0.2 built=2024-10-01T15:13:28Z pid=37477 go=go1.23.1 arch=arm64
2024/10/01 17:13:29 🥫 Open database file=~/.billy-idle/default/db.sqlite3 sqlite=3.46.0
2024/10/01 17:13:29 👀 Tracker started in idle mode with auto-idle>=2m0s interval=10s
2024/10/01 17:13:29 🐝 Enter busy mode after 0s idle time rec=#13
2024/10/01 17:15:33 💤 Checkpoint idleTime=11s state=idle lastSwitch=0s ago lastCheck=5s ago
2024/10/01 17:15:38 🛑 Received signal interrupt, initiate shutdown
2024/10/01 17:15:38 🛑 Tracker stopped after 0s busy time rec=#10
2024/10/01 17:15:38 🥫 Close database in ~/.billy-idle/dev
----

NOTE: Binary packages for macOS amd64 and arm64 are coming soon, the same goes for docker images on ghcr.io
Expand All @@ -51,21 +71,22 @@ $ launchctl unload ~/Library/LaunchAgents/com.github.tillkuhn.billy-idle.plist

[source,shell]
----
$ billy help
$ billy track -help
-app-dir string
App Directory e.g. for SQLite DB (defaults to $HOME/.billy-idle/<env>
-cmd string
Command to retrieve HIDIdleTime (default "ioreg")
-db-dir string
SQLite directory (default "./sqlite")
-debug
Debug checkpoints
-drop-create
Drop and re-create db schema (CAUTION!)
Drop and re-create db schema on startup
-env string
Environment (default "default")
-idle duration
Max time before client is considered idle (default 10s)
Max tolerated idle time before client enters idle state (default 10s)
-interval duration
Interval to check for idle time (default 2s)
----

== Database Support
Expand All @@ -84,12 +105,21 @@ Usage: make <OPTIONS> ... <TARGETS>
Available targets are:
build build all targets
build-mac build for mac current arch using default goreleaser target path
clean Clean output directory
help Shows the help
install Install as launchd managed service
lint Lint go code
logs Show agent logs
minor Create Minor Release
run Run tracker
test Test go code
release run goreleaser in snapshot mode
report Show report for default db
report-dev Show report for dev db
run Run app in tracker mode, add -drop-create to recreate db
run-help Run app in help mode
run-mac run mac build
test Run tests with coverage, implies lint
tidy Add missing and remove unused modules
update Update all go dependencies
----

Expand Down
12 changes: 8 additions & 4 deletions agent.plist
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@
<array>
<string>$HOME/bin/billy</string>
<string>track</string>
<string>-db-dir</string>
<string>$HOME/.billy-idle</string>
<string>-idle</string>
<string>2m</string>
<string>-interval</string>
<string>10s</string>

<!--
<string>-db-dir</string>
<string>$HOME/.billy-idle</string>
-->

<!-- optional -->
<string>-debug</string>
</array>
<key>StandardOutPath</key><string>$HOME/.billy-idle/agent.log</string>
<key>StandardErrorPath</key><string>$HOME/.billy-idle/agent.log</string>
<key>StandardOutPath</key><string>$HOME/.billy-idle/default/agent.log</string>
<key>StandardErrorPath</key><string>$HOME/.billy-idle/default/agent.log</string>
<key>Debug</key><true/>
<key>RunAtLoad</key><true/>
</dict>
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ go 1.23.1

require (
github.com/brianvoe/gofakeit/v7 v7.0.4
github.com/jmoiron/sqlx v1.4.0
github.com/stretchr/testify v1.9.0
modernc.org/sqlite v1.33.1
)

require (
github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
Expand Down
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/brianvoe/gofakeit/v7 v7.0.4 h1:Mkxwz9jYg8Ad8NvT9HA27pCMZGFQo08MK6jD0QTKEww=
github.com/brianvoe/gofakeit/v7 v7.0.4/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
32 changes: 25 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func main() {
var opts tracker.Options
trackCmd := flag.NewFlagSet("track", flag.ExitOnError)
trackCmd.StringVar(&opts.Cmd, "cmd", "ioreg", "Command to retrieve HIDIdleTime")
trackCmd.StringVar(&opts.DbDirectory, "db-dir", "./sqlite", "SQLite directory")
trackCmd.StringVar(&opts.AppDir, "app-dir", "", "App Directory e.g. for SQLite DB (defaults to $HOME/.billy-idle/<env>")
trackCmd.BoolVar(&opts.Debug, "debug", false, "Debug checkpoints")
trackCmd.BoolVar(&opts.DropCreate, "drop-create", false, "Drop and re-create db schema on startup")
trackCmd.StringVar(&opts.Env, "env", "default", "Environment")
Expand All @@ -50,22 +50,40 @@ func main() {
os.Args = append(os.Args, "help")
}
switch os.Args[1] {
case "track":
_ = trackCmd.Parse(os.Args[2:])
if *trackCmd.Bool("h", false, "Show help") {
trackCmd.PrintDefaults()
break
case "track", "report":
if err := trackCmd.Parse(os.Args[2:]); err != nil {
log.Fatal(err) // -h and -help will print usage implicitly
}
if opts.AppDir == "" {
opts.AppDir = defaultAppDir(opts.Env)
}
t := tracker.New(&opts)
// todo: make a real case out of this mess :-)
if os.Args[1] == "report" {
_ = t.Report(ctx, os.Stdout)
break
}
go func() {
t.Track(ctx)
}()
sig := <-sigChan
log.Printf("🛑 Received Signal %v", sig)
log.Printf("🛑 Received signal %v, initiate shutdown", sig)
ctxCancel()
t.WaitClose()
default:
fmt.Printf("Usage: %s [command]\n\nAvailable Commands (more coming soon):\n track Starts the tracker\n\n", app)
fmt.Printf("Use \"%s [command] -h\" for more information about a command.\n", app)
}
}

func defaultAppDir(env string) string {
home, err := os.UserHomeDir() // $HOME on *nix
if err != nil {
log.Fatal(err)
}
dir := filepath.Join(home, ".billy-idle", env)
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
log.Fatal(err)
}
return dir
}
10 changes: 7 additions & 3 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ func Test_Tracker(t *testing.T) {
opts := &tracker.Options{
CheckInterval: 100 * time.Millisecond,
IdleTolerance: 100 * time.Millisecond,
DbDirectory: dir, // overwrite with tempdir
Cmd: "testdata/ioreg-mock.sh",
AppDir: dir, // overwrite with tempdir
Cmd: "pkg/tracker/ioreg_mock.sh",
}
tr := tracker.New(opts)
assert.NoError(t, err)
Expand All @@ -39,6 +39,10 @@ func Test_Tracker(t *testing.T) {
}

func Test_Help(_ *testing.T) {
os.Args = []string{"hase", "help"}
os.Args = []string{"app", "help"}
main()
}

func Test_DefaultAppDir(t *testing.T) {
assert.NotEmpty(t, defaultAppDir("test"))
}
4 changes: 2 additions & 2 deletions pkg/tracker/init-db.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ CREATE TABLE IF NOT EXISTS track (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"busy_start" DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime')),
"busy_end" DATETIME,
"client" TEXT,
"message" TEXT,
"task" TEXT );
"task" TEXT,
"client" TEXT );
-- SQLite does not support add column if not exists https://stackoverflow.com/q/3604310/4292075
-- ALTER TABLE track ADD COLUMN IF NOT EXISTS task TEXT;
Loading

0 comments on commit 1687516

Please sign in to comment.