7 Commits

Author SHA1 Message Date
Gregory Ballantine
cb55a19ada Switching from air to fresh for auto-reloading
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is pending
2025-10-02 14:42:14 -04:00
Gregory Ballantine
352950467c Finishing the test/edit route
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is pending
2025-10-02 11:28:56 -04:00
Gregory Ballantine
abc4abe80e Updating Woodpecker CI config 2025-10-02 11:25:14 -04:00
Gregory Ballantine
ff8acd493b [Issue #2] - Added .air.toml for using air with some sane defaults; added README to document how to use it (plus some info on the app) 2025-10-02 11:22:21 -04:00
Gregory Ballantine
4b98322022 Continued work on the test/edit post routet
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-08-12 11:40:56 -04:00
60d8554cf1 Updated the test edit page (still need to do the post page)
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-08-10 01:44:33 -04:00
c19bb2108c Adding local tmp directory to git ignore
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-28 08:00:21 -04:00
10 changed files with 161 additions and 4 deletions

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@ blt
# Local data files
data/
tmp/
# Compiled assets
public/css/

View File

@@ -1,4 +1,4 @@
pipeline:
steps:
build:
image: golang:1.22
commands:

54
README.md Normal file
View File

@@ -0,0 +1,54 @@
# Benchmark Logging Tool (BLT)
![Build badge](https://builds.metaunix.net/api/badges/87/status.svg)
Web-based tool to store and organize PC hardware benchmarks.
## Project Goals
The goals of this project are to:
* Record benchmarking results from multiple devices - e.g. log from a laptop or a phone.
* Group results into tests to keep track of different testing configurations.
* Encourage running tests multiple times - it's good practice to run a benchmark multiple times for accuracy.
* Create comparisons of hardware tests to compare performance.
* Generate graphs of hardware comparisons for usage in videos and articles.
## Requirements
BLT runs on Go. It uses the built-in `go mod` tool to manage dependencies, thus there is no external tooling to install to build/run BLT.
Debian/Ubuntu: `apt install -y golang`
RedHat and clones: `dnf install -y golang`
## Production Deployment
**TODO**
## Development
### Via Docker
**TODO**
### Local/Native Development
BLT uses [fresh](https://github.com/gravityblast/fresh) to auto-reload the app. While this is not strictly necessary, it used to make development more convenient. If you wish to forego installing it, you may simply build and run the app with the standard `go run main.go`.
1. Install dependencies:
`go mod download`
2. Install fresh to auto-reload the app:
`go install github.com/gravityblast/fresh@latest`
3. Run the app via air:
`fresh`
4. If everything is running successfully you can open your browser and go to http://localhost:2830.
## License
This project is available under the BSD 2-Clause license.

View File

@@ -1,6 +1,8 @@
package models
import (
"strconv"
"gorm.io/gorm"
)
@@ -16,3 +18,7 @@ type Benchmark struct {
// has many results
Results []Result
}
func (b *Benchmark) StringID() string {
return strconv.Itoa(int(b.ID))
}

View File

@@ -1,6 +1,8 @@
package models
import (
"strconv"
"gorm.io/gorm"
)
@@ -19,3 +21,24 @@ type Test struct {
// has many results
Results []Result
}
func (t *Test) SelectedBenchmarks() []string {
benchmarks := t.Benchmarks
ids := make([]string, len(benchmarks))
for i, b := range benchmarks {
ids[i] = strconv.Itoa(int(b.ID))
}
return ids
}
func (t *Test) IsBenchmarkSelected(benchmarkID uint) bool {
benchmarkUint := uint(benchmarkID)
for _, b := range t.Benchmarks {
if b.ID == benchmarkUint {
return true
}
}
return false
}

14
runner.conf Normal file
View File

@@ -0,0 +1,14 @@
root: .
tmp_path: ./tmp
build_name: runner-build
build_log: runner-build-errors.log
valid_ext: .go, .tpl, .tmpl, .html
no_rebuild_ext: .tpl, .tmpl, .html
ignored: assets, tmp, node_modules, data, vendor
build_delay: 600
colors: 1
log_color_main: cyan
log_color_build: yellow
log_color_runner: green
log_color_watcher: magenta
log_color_app:

View File

@@ -30,9 +30,9 @@
<label for="test_benchmarks">
Benchmarks to Test:
<select id="test_benchmarks" class="u-full-width" name="test_benchmarks" multiple>
{{ $testBenchmarks := .test.Benchmarks }}
{{ $selectedBenchmarks := .selectedBenchmarks }}
{{ range $bm := .benchmarks }}
<option value="{{ $bm.ID }}" {{ if contains $testBenchmarks $bm.ID }}selected{{ end }}>{{ $bm.Name }}</option>
<option value="{{ $bm.ID }}" {{ if contains $selectedBenchmarks $bm.StringID }}selected{{ end }}>{{ $bm.Name }}</option>
{{ end }}
</select>
</label>

View File

@@ -4,5 +4,15 @@ type TestForm struct {
Name string `form:"test_name" validate:"required"`
Description string `form:"test_description"`
Hardware int `form:"test_hardware" validate:"required"`
Benchmarks []string `form:"test_benchmarks" validate:"required"`
Benchmarks []uint `form:"test_benchmarks" validate:"required"`
}
func (t *TestForm) IsBenchmarkSelected(checkID uint) bool {
for _, selectedID := range t.Benchmarks {
if checkID == selectedID {
return true
}
}
return false
}

View File

@@ -54,6 +54,7 @@ func RegisterRoutes(f *flamego.Flame) {
f.Group("/{test_id}", func() {
f.Get("", routes.TestGetView)
f.Get("/edit", routes.TestGetEdit)
f.Post("/edit", binding.Form(forms.TestForm{}), routes.TestPostEdit)
})
})

View File

@@ -99,6 +99,54 @@ func TestGetEdit(c flamego.Context, t template.Template, data template.Data) {
models.DB.Find(&benchmarks)
data["benchmarks"] = benchmarks
// determine which benchmarks are selected in a test
selectedBenchmarks := test.SelectedBenchmarks()
data["selectedBenchmarks"] = selectedBenchmarks
data["title"] = fmt.Sprintf("Editing Test: %s", test.Name)
t.HTML(http.StatusOK, "test/edit")
}
func TestPostEdit(c flamego.Context, form forms.TestForm, errs binding.Errors) {
if len(errs) > 0 {
var err error
switch errs[0].Category {
case binding.ErrorCategoryValidation:
err = errs[0].Err.(validator.ValidationErrors)[0]
default:
err = errs[0].Err
}
log.Fatal(err)
}
// find test ID from request
testID := c.Param("test_id")
// find hardware from DB
var test models.Test
models.DB.Preload("Hardware").Preload("Benchmarks").First(&test, testID)
test.Name = form.Name
test.Description = form.Description
test.HardwareID = form.Hardware
// bind benchmarks to test that aren't already associated
for _, b := range form.Benchmarks {
if ! test.IsBenchmarkSelected(b) {
var benchmark models.Benchmark
models.DB.First(&benchmark, b) // find benchmark
models.DB.Model(&test).Association("Benchmarks").Append(&benchmark)
}
}
// removed associated benchmarks that weren't in the form
for _, b := range test.Benchmarks {
if ! form.IsBenchmarkSelected(b.ID) {
var benchmark models.Benchmark
models.DB.First(&benchmark, b) // find benchmark
models.DB.Model(&test).Association("Benchmarks").Delete(&benchmark)
}
}
c.Redirect(fmt.Sprintf("/test/%d", test.ID))
}