Skip to content
This repository was archived by the owner on Mar 3, 2023. It is now read-only.

Commit e24acf0

Browse files
committedOct 2, 2022
feature: cli-based implementation for viewing statistics
1 parent dbbfa23 commit e24acf0

File tree

4 files changed

+543
-0
lines changed

4 files changed

+543
-0
lines changed
 

‎cmd/statistics/helper.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/manifoldco/promptui"
7+
)
8+
9+
var monthNames map[string]string = map[string]string{
10+
"01": "January",
11+
"02": "February",
12+
"03": "March",
13+
"04": "April",
14+
"05": "May",
15+
"06": "June",
16+
"07": "July",
17+
"08": "August",
18+
"09": "September",
19+
"10": "October",
20+
"11": "November",
21+
"12": "December",
22+
}
23+
24+
func transformMonthName(months ...string) []string {
25+
var names []string
26+
27+
for _, month := range months {
28+
names = append(names, fmt.Sprintf("%s - %s", month, monthNames[month]))
29+
}
30+
31+
return names
32+
}
33+
34+
func selectPrompt(label string, items ...string) (int, string, error) {
35+
ps := promptui.Select{
36+
Label: label,
37+
Items: items,
38+
}
39+
40+
index, result, err := ps.Run()
41+
42+
if err != nil {
43+
if err == promptui.ErrInterrupt {
44+
return -1, "", nil
45+
}
46+
47+
return -1, "", fmt.Errorf("failed to run prompt: %w", err)
48+
}
49+
50+
return index, result, nil
51+
}

‎cmd/statistics/main.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package main
2+
3+
import (
4+
"database/sql"
5+
"flag"
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
"time"
10+
11+
"github.com/omarabdelaz1z/go-monitor/cmd/config"
12+
"github.com/omarabdelaz1z/go-monitor/cmd/helper"
13+
14+
"github.com/omarabdelaz1z/go-monitor/cmd/provider"
15+
"github.com/omarabdelaz1z/go-monitor/internal/model"
16+
"github.com/rs/zerolog"
17+
)
18+
19+
func main() {
20+
var (
21+
db *sql.DB
22+
err error
23+
file *os.File
24+
cfg *config.Config = &config.Config{}
25+
)
26+
27+
flag.StringVar(&cfg.Db.Driver, "driver", os.Getenv("DB_DRIVER"), "database driver")
28+
flag.StringVar(&cfg.Db.Dsn, "dsn", os.Getenv("DB_DSN"), "database dsn")
29+
flag.IntVar(&cfg.Db.MaxIdleConns, "max-idle-conns", 5, "max idle connections")
30+
flag.IntVar(&cfg.Db.MaxOpenConns, "max-open-conns", 10, "max open connections")
31+
flag.IntVar(&cfg.Db.MaxIdleTime, "max-idle-time", 2, "max idle time")
32+
33+
flag.StringVar(&cfg.Log.Path, "log-path", os.Getenv("LOG_PATH"), "log path")
34+
35+
helper.EnumFlag(&cfg.Log.Level, "log-level", []string{"debug", "info", "warn", "error"}, "log level")
36+
flag.Parse()
37+
38+
logLevel := zerolog.Level(helper.GetLevel(cfg.Log.Level))
39+
40+
file, err = os.OpenFile(cfg.Log.Path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
41+
42+
if err != nil {
43+
panic(err)
44+
}
45+
46+
logger := zerolog.New(zerolog.MultiLevelWriter(file, zerolog.ConsoleWriter{
47+
Out: os.Stdout,
48+
TimeFormat: time.RFC1123,
49+
FormatCaller: func(i interface{}) string {
50+
if i == nil {
51+
return ""
52+
}
53+
return filepath.Base(fmt.Sprintf("%+v", i))
54+
},
55+
})).Level(logLevel).With().Timestamp().Logger()
56+
57+
logger.Debug().Msg("initiating connection to database")
58+
59+
db, err = provider.NewDatabase(&provider.DbConfig{
60+
Driver: cfg.Db.Driver,
61+
Dsn: cfg.Db.Dsn,
62+
MaxIdleConns: cfg.Db.MaxIdleConns,
63+
MaxOpenConns: cfg.Db.MaxOpenConns,
64+
MaxIdleTime: cfg.Db.MaxIdleTime,
65+
})
66+
67+
if err != nil {
68+
logger.Fatal().Err(err).Msg("failed to initiate connection to database")
69+
return
70+
}
71+
72+
logger.Info().Msg("connected to database")
73+
74+
defer db.Close()
75+
76+
service := &Service{
77+
snapshots: model.NewSnapshotModel(db),
78+
config: cfg,
79+
logger: logger,
80+
monthSafeList: []string{},
81+
}
82+
83+
if err = service.Run(); err != nil {
84+
logger.Fatal().Err(err).Msg("error occrred while running service")
85+
return
86+
}
87+
88+
logger.Info().Msg("service stopped successfully")
89+
}

‎cmd/statistics/service.go

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"os/signal"
8+
"syscall"
9+
"time"
10+
11+
"github.com/jedib0t/go-pretty/v6/table"
12+
"github.com/omarabdelaz1z/go-monitor/cmd/config"
13+
"github.com/omarabdelaz1z/go-monitor/internal/model"
14+
"github.com/omarabdelaz1z/go-monitor/internal/util"
15+
16+
"github.com/rs/zerolog"
17+
"golang.org/x/sync/errgroup"
18+
)
19+
20+
type Service struct {
21+
snapshots *model.SnapshotModel
22+
config *config.Config
23+
logger zerolog.Logger
24+
25+
monthSafeList []string
26+
}
27+
28+
func (s *Service) Run() error {
29+
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
30+
defer stop()
31+
32+
g, gCtx := errgroup.WithContext(ctx)
33+
34+
months, err := s.snapshots.GetMonthsInYear(gCtx, fmt.Sprint(time.Now().Year()))
35+
36+
if err != nil {
37+
return fmt.Errorf("failed to get months: %w", err)
38+
}
39+
40+
s.monthSafeList = transformMonthName(months...)
41+
42+
g.Go(func() error {
43+
return s.Respond(gCtx)
44+
})
45+
46+
if err = g.Wait(); err != nil {
47+
s.logger.Error().Err(err).Caller().Msg("service failed while running")
48+
return fmt.Errorf("service failed while running: %w", err)
49+
}
50+
51+
return nil
52+
}
53+
54+
func (s *Service) Respond(ctx context.Context) error {
55+
t := table.NewWriter()
56+
t.SetOutputMirror(os.Stdout)
57+
58+
for {
59+
select {
60+
case <-ctx.Done():
61+
return nil
62+
default:
63+
option, _, err := selectPrompt("What would you like to do?", "View today's stats", "View stats for a month", "View all stats", "Exit")
64+
65+
if option == -1 && nil == err {
66+
return nil
67+
}
68+
69+
if err != nil {
70+
return err
71+
}
72+
73+
switch option {
74+
case 0:
75+
err = s.HandleTodayStats(ctx, t)
76+
77+
if err != nil {
78+
switch err {
79+
case model.ErrNoRows:
80+
fmt.Println("No stats for today")
81+
case model.ErrTimedOut:
82+
fmt.Println("Timed out while fetching stats")
83+
default:
84+
return err
85+
}
86+
}
87+
88+
t.Render()
89+
case 1:
90+
err = s.HandleMonthStats(ctx, t)
91+
92+
if err != nil {
93+
return err
94+
}
95+
96+
t.Render()
97+
case 2:
98+
err = s.HandleAllStats(ctx, t)
99+
100+
if err != nil {
101+
return err
102+
}
103+
104+
t.Render()
105+
case 3:
106+
return nil
107+
}
108+
109+
t.ResetHeaders()
110+
t.ResetRows()
111+
t.ResetFooters()
112+
}
113+
}
114+
}
115+
116+
func (s *Service) HandleMonthStats(ctx context.Context, t table.Writer) error {
117+
var (
118+
err error
119+
option string
120+
dailyStats []model.Snapshot
121+
)
122+
123+
_, option, err = selectPrompt("Which month would you like to view?", s.monthSafeList...)
124+
125+
if err != nil {
126+
return err
127+
}
128+
129+
if nil == err && option == "" {
130+
return nil
131+
}
132+
133+
dailyStats, err = s.snapshots.GetStatsByMonth(ctx, option[:2])
134+
135+
if err != nil {
136+
return fmt.Errorf("failed to get stats by month: %w", err)
137+
}
138+
139+
t.SetCaption(fmt.Sprintf("Stats for %s", option[5:]))
140+
t.AppendHeader(table.Row{"Date", "Uploaded", "Downloaded", "Total"})
141+
142+
for i := 0; i < len(dailyStats)-1; i++ {
143+
t.AppendRow(table.Row{
144+
time.Unix(dailyStats[i].Timestamp, 0).Format("2006-01-02"),
145+
util.ByteCountSI(dailyStats[i].Stat.Sent),
146+
util.ByteCountSI(dailyStats[i].Stat.Received),
147+
util.ByteCountSI(dailyStats[i].Stat.Total),
148+
})
149+
}
150+
151+
t.AppendSeparator()
152+
t.AppendFooter(table.Row{
153+
"Cumulative",
154+
util.ByteCountSI(dailyStats[len(dailyStats)-1].Stat.Sent),
155+
util.ByteCountSI(dailyStats[len(dailyStats)-1].Stat.Received),
156+
util.ByteCountSI(dailyStats[len(dailyStats)-1].Stat.Total),
157+
})
158+
159+
return nil
160+
}
161+
162+
func (s *Service) HandleAllStats(ctx context.Context, t table.Writer) error {
163+
stats, err := s.snapshots.GetAllStats(ctx)
164+
165+
if err != nil {
166+
return fmt.Errorf("failed to get stats: %w", err)
167+
}
168+
169+
t.SetCaption("All stats")
170+
t.AppendHeader(table.Row{"Month", "Uploaded", "Downloaded", "Total"})
171+
172+
for _, stat := range stats {
173+
t.AppendRow(table.Row{
174+
time.Unix(stat.Timestamp, 0).Format("2006-01-02"),
175+
util.ByteCountSI(stat.Stat.Sent),
176+
util.ByteCountSI(stat.Stat.Received),
177+
util.ByteCountSI(stat.Stat.Total),
178+
})
179+
}
180+
181+
return nil
182+
}
183+
184+
func (s *Service) HandleTodayStats(ctx context.Context, t table.Writer) error {
185+
today := time.Now().Format("2006-01-02")
186+
187+
stat, err := s.snapshots.GetStatByDate(ctx, today)
188+
189+
if err != nil {
190+
return err
191+
}
192+
193+
t.SetCaption(fmt.Sprintf("Monitored %d hours on %s", stat.HoursMonitored, today))
194+
t.AppendHeader(table.Row{"Uploaded", "Downloaded", "Total"})
195+
196+
t.AppendRow(table.Row{
197+
util.ByteCountSI(stat.Stat.Sent),
198+
util.ByteCountSI(stat.Stat.Received),
199+
util.ByteCountSI(stat.Stat.Total),
200+
})
201+
202+
return nil
203+
}

0 commit comments

Comments
 (0)
This repository has been archived.