1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
package main
import (
"database/sql"
"flag"
"fmt"
"log/slog"
"os"
"github.com/grafana/pyroscope-go"
"github.com/martinlehoux/biking_home/config"
"github.com/martinlehoux/biking_home/mountain_pass"
"github.com/martinlehoux/biking_home/ride"
"github.com/martinlehoux/biking_home/web"
"github.com/martinlehoux/kagamigo/kcore"
_ "github.com/mattn/go-sqlite3"
)
var (
configFile = flag.String("config", "config.yaml", "path to the YAML configuration file")
enablePprof = flag.Bool("pprof", false, "expose pprof endpoints at /debug/pprof")
)
func main() {
flag.Parse()
appConfig, err := config.Load(*configFile)
kcore.Expect(err, "failed to load configuration")
stopProfiler, err := startPyroscope(appConfig.Pyroscope)
if err != nil {
kcore.Expect(err, "failed to start pyroscope")
}
if stopProfiler != nil {
defer stopProfiler()
}
db, err := sql.Open("sqlite3", appConfig.Database.Path)
kcore.Expect(err, "failed to open database")
defer db.Close()
runCommand(db, *configFile, appConfig, flag.Args(), *enablePprof)
}
func runCommand(db *sql.DB, configPath string, appConfig config.Config, args []string, enablePprof bool) {
if len(args) == 0 {
web.ServeCmd(db, configPath, appConfig, enablePprof)
return
}
switch args[0] {
case "fetch-osm":
mountain_pass.FetchOSM()
case "extract-osm":
if len(args) < 2 {
fmt.Fprintln(os.Stderr, "extract-osm requires a PBF file path argument")
printUsage()
os.Exit(2)
}
mountain_pass.ExtractOSM(db, args[1])
case "backfill":
ride.BackfillCmd(db)
case "climbs-backfill":
web.BackfillOfficialClimbAttemptsCmd(db, configPath)
case "backtest":
ride.BacktestCmd(db)
case "elevation":
ride.ElevationCmd(db, args[1:])
case "serve":
for _, arg := range args[1:] {
if arg == "--pprof" || arg == "-pprof" || arg == "pprof" {
enablePprof = true
}
}
web.ServeCmd(db, configPath, appConfig, enablePprof)
case "help", "-h", "--help":
printUsage()
default:
fmt.Fprintf(os.Stderr, "unknown command %q\n", args[0])
printUsage()
os.Exit(2)
}
}
func printUsage() {
fmt.Fprintf(os.Stderr, `Usage: biking_home [global flags] <command> [args]
Global flags:
-config <path> path to YAML configuration file (default "config.yaml")
-pprof expose pprof endpoints at /debug/pprof (also: "serve --pprof")
Commands:
serve start web server (default when no command given)
fetch-osm download the France OSM PBF into france-latest.osm.pbf
extract-osm <path> extract mountain passes from an OSM PBF file
backfill recompute all stored ride values
climbs-backfill record official climb attempts for every stored ride
backtest run data benchmarks comparing computed values against stored reference values
elevation analyse detect elevation outliers (tunnel spikes)
elevation fix linearly fix elevation between anchors
`)
}
// startPyroscope starts the continuous profiler when enabled in the config and
// returns a stop function to flush and detach the profiler on shutdown, or nil
// when pyroscope is disabled.
func startPyroscope(appConfig config.PyroscopeConfig) (func(), error) {
if !appConfig.Enabled {
return nil, nil
}
profiler, err := pyroscope.Start(pyroscope.Config{
ApplicationName: appConfig.ApplicationName,
ServerAddress: appConfig.ServerAddress,
Logger: pyroscope.StandardLogger,
ProfileTypes: []pyroscope.ProfileType{
pyroscope.ProfileCPU,
pyroscope.ProfileAllocSpace,
pyroscope.ProfileAllocObjects,
pyroscope.ProfileInuseSpace,
pyroscope.ProfileInuseObjects,
pyroscope.ProfileGoroutines,
},
})
if err != nil {
return nil, err
}
slog.Info("pyroscope enabled", "server", appConfig.ServerAddress, "application", appConfig.ApplicationName)
return func() { _ = profiler.Stop() }, nil
}
|