aboutsummaryrefslogtreecommitdiff
path: root/main.go
blob: d475a906be4678feb7281c094767d463190075f6 (plain) (blame)
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

package main

import (
	"database/sql"
	"flag"
	"fmt"
	"log/slog"
	"os"

	"github.com/grafana/pyroscope-go"
	"github.com/martinlehoux/biking_home/backfill"
	"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":
		backfill.Run(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 stored ride values and record official climb attempts
  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
}