11package main
22
33import (
4- "encoding/json"
5- "fmt"
4+ "fmt"
65 "net"
76 "os"
87 "path/filepath"
@@ -12,13 +11,38 @@ import (
1211)
1312
1413// pingping 2.0: a probe, text files, and a smoke graph. Nothing else.
14+ // Config: v2.1 has no config file. These are the program's constants —
15+ // the only things a user edits are targets/ping.list and tcp.list.
1516type Config struct {
16- Listen string `json:"listen"`
17- DataDir string `json:"data_dir"`
18- TargetsDir string `json:"targets_dir"`
19- Targets []TargetCfg `json:"targets"`
20- Probe ProbeCfg `json:"probe"`
21- RetentionDays int `json:"retention_days"` // raw JSONL kept this long, then rm
17+ Listen string
18+ DataDir string
19+ TargetsDir string
20+ Targets []TargetCfg
21+ Probe ProbeCfg
22+ RetentionDays int
23+ }
24+
25+ func defaultConfig () * Config {
26+ return & Config {
27+ Listen : "0.0.0.0:8517" ,
28+ DataDir : "./data" ,
29+ TargetsDir : "./targets" ,
30+ Probe : ProbeCfg {IntervalSec : 60 , Packets : 20 , GapMs : 50 , TimeoutMs : 1000 },
31+ RetentionDays : 300 ,
32+ }
33+ }
34+
35+ // FinishConfig loads targets from the lists and validates them.
36+ func FinishConfig (cfg * Config ) error {
37+ listTargets , err := loadTargetLists (cfg .TargetsDir )
38+ if err != nil {
39+ return err
40+ }
41+ cfg .Targets = append (cfg .Targets , listTargets ... )
42+ if len (cfg .Targets ) == 0 {
43+ return fmt .Errorf ("no targets: add one line to %s/ping.list or tcp.list" , cfg .TargetsDir )
44+ }
45+ return validateTargets (cfg )
2246}
2347
2448type TargetCfg struct {
@@ -40,64 +64,40 @@ type ProbeCfg struct {
4064
4165var dirSan = regexp .MustCompile (`[^a-zA-Z0-9._\p{Han}-]` )
4266
43- func LoadConfig (path string ) (* Config , error ) {
44- raw , err := os .ReadFile (path )
45- if err != nil {
46- return nil , err
47- }
48- cfg := & Config {
49- Listen : "0.0.0.0:8517" ,
50- DataDir : "./data" ,
51- Probe : ProbeCfg {IntervalSec : 60 , Packets : 20 , GapMs : 50 , TimeoutMs : 1000 },
52- RetentionDays : 300 ,
53- }
54- if err := json .Unmarshal (stripJSONC (raw ), cfg ); err != nil {
55- return nil , fmt .Errorf ("parse %s: %w" , path , err )
56- }
57- if cfg .TargetsDir == "" {
58- cfg .TargetsDir = "./targets"
59- }
60- listTargets , err := loadTargetLists (cfg .TargetsDir )
61- if err != nil {
62- return nil , err
63- }
64- cfg .Targets = append (cfg .Targets , listTargets ... )
65- if len (cfg .Targets ) == 0 {
66- return nil , fmt .Errorf ("no targets: add one to %s/ping.list or tcp.list" , cfg .TargetsDir )
67- }
67+ func validateTargets (cfg * Config ) error {
6868 seen := map [string ]bool {}
6969 for i := range cfg .Targets {
7070 t := & cfg .Targets [i ]
7171 if t .Name == "" {
72- return nil , fmt .Errorf ("targets[%d] missing name" , i )
72+ return fmt .Errorf ("target #%d has no name" , i + 1 )
7373 }
74- switch t .Type {
75- case "" , "icmp" :
74+ if t .Type == "" {
7675 t .Type = "icmp"
76+ }
77+ switch t .Type {
78+ case "icmp" :
7779 case "tcp" :
7880 if t .Port <= 0 {
79- return nil , fmt .Errorf ("tcp target %q needs a port" , t .Name )
81+ return fmt .Errorf ("tcp target %q needs a port" , t .Name )
8082 }
8183 default :
82- return nil , fmt .Errorf ("target %q: unknown type %q (icmp|tcp) " , t .Name , t .Type )
84+ return fmt .Errorf ("target %q: unknown type %q" , t .Name , t .Type )
8385 }
8486 switch t .Pace {
8587 case "" , "fast" , "normal" , "slow" :
8688 default :
87- return nil , fmt .Errorf ("target %q: invalid pace %q (fast|slow)" , t .Name , t .Pace )
89+ return fmt .Errorf ("target %q: invalid pace %q (fast|slow)" , t .Name , t .Pace )
8890 }
8991 t .dir = dirSan .ReplaceAllString (t .Name , "_" )
9092 if seen [t .dir ] {
91- return nil , fmt .Errorf ("target name %q collides with another target " , t .Name )
93+ return fmt .Errorf ("duplicate target name: %q" , t .Name )
9294 }
9395 seen [t .dir ] = true
9496 }
95- if cfg .Probe .Packets < 3 {
96- cfg .Probe .Packets = 3 // fewer than 3 samples is not a distribution
97- }
98- return cfg , nil
97+ return nil
9998}
10099
100+
101101// loadTargetLists reads targets/ping.list and tcp.list.
102102// line format: host[:port] [name...] [pace=fast|slow] [interval=sec]
103103func loadTargetLists (dir string ) ([]TargetCfg , error ) {
@@ -165,41 +165,3 @@ func parseListLine(line, typ string) (TargetCfg, error) {
165165 return t , nil
166166}
167167
168- func stripJSONC (b []byte ) []byte {
169- var out strings.Builder
170- out .Grow (len (b ))
171- inStr , esc := false , false
172- for i := 0 ; i < len (b ); i ++ {
173- c := b [i ]
174- if inStr {
175- out .WriteByte (c )
176- if esc {
177- esc = false
178- } else if c == '\\' {
179- esc = true
180- } else if c == '"' {
181- inStr = false
182- }
183- continue
184- }
185- switch {
186- case c == '"' :
187- inStr = true
188- out .WriteByte (c )
189- case c == '/' && i + 1 < len (b ) && b [i + 1 ] == '/' :
190- for i < len (b ) && b [i ] != '\n' {
191- i ++
192- }
193- out .WriteByte ('\n' )
194- case c == '/' && i + 1 < len (b ) && b [i + 1 ] == '*' :
195- i += 2
196- for i + 1 < len (b ) && ! (b [i ] == '*' && b [i + 1 ] == '/' ) {
197- i ++
198- }
199- i ++
200- default :
201- out .WriteByte (c )
202- }
203- }
204- return []byte (out .String ())
205- }
0 commit comments