-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add profile. #53
Add profile. #53
Changes from 4 commits
767df00
7703197
882dcfb
eb71ddd
7d84a0e
b9aaebd
ffbc32d
8f4409f
ba3b2bd
e85a409
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -9,8 +9,11 @@ import ( | |||||
"os" | ||||||
"os/signal" | ||||||
"path/filepath" | ||||||
"runtime" | ||||||
"runtime/pprof" | ||||||
"strings" | ||||||
"syscall" | ||||||
"time" | ||||||
|
||||||
"github.com/bwplotka/mdox/pkg/clilog" | ||||||
"github.com/bwplotka/mdox/pkg/extkingpin" | ||||||
|
@@ -20,11 +23,16 @@ import ( | |||||
"github.com/bwplotka/mdox/pkg/transform" | ||||||
"github.com/bwplotka/mdox/pkg/version" | ||||||
"github.com/charmbracelet/glamour" | ||||||
"github.com/efficientgo/tools/core/pkg/errcapture" | ||||||
"github.com/efficientgo/tools/core/pkg/logerrcapture" | ||||||
extflag "github.com/efficientgo/tools/extkingpin" | ||||||
"github.com/felixge/fgprof" | ||||||
"github.com/go-kit/kit/log" | ||||||
"github.com/go-kit/kit/log/level" | ||||||
"github.com/oklog/run" | ||||||
"github.com/pkg/errors" | ||||||
"github.com/prometheus/client_golang/prometheus" | ||||||
"github.com/prometheus/common/expfmt" | ||||||
"gopkg.in/alecthomas/kingpin.v2" | ||||||
) | ||||||
|
||||||
|
@@ -34,6 +42,11 @@ const ( | |||||
logFormatCLILog = "clilog" | ||||||
) | ||||||
|
||||||
type mdoxMetrics struct { | ||||||
reg *prometheus.Registry | ||||||
dir string | ||||||
} | ||||||
|
||||||
func setupLogger(logLevel, logFormat string) log.Logger { | ||||||
var lvl level.Option | ||||||
switch logLevel { | ||||||
|
@@ -66,14 +79,33 @@ func main() { | |||||
Default("info").Enum("error", "warn", "info", "debug") | ||||||
logFormat := app.Flag("log.format", "Log format to use."). | ||||||
Default(logFormatCLILog).Enum(logFormatLogfmt, logFormatJson, logFormatCLILog) | ||||||
// Profiling and metrics. | ||||||
profilesPath := app.Flag("debug.profiles", "Path to which CPU and heap profiles are saved").Hidden().String() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||||||
metrics := app.Flag("metrics", "Path to which metrics are saved in OpenMetrics format").Hidden().String() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto for above changes (: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||||||
|
||||||
m := &mdoxMetrics{} | ||||||
|
||||||
ctx, cancel := context.WithCancel(context.Background()) | ||||||
registerFmt(ctx, app) | ||||||
registerFmt(ctx, app, m) | ||||||
registerTransform(ctx, app) | ||||||
|
||||||
cmd, runner := app.Parse() | ||||||
logger := setupLogger(*logLevel, *logFormat) | ||||||
|
||||||
if *metrics != "" { | ||||||
m.dir = *metrics | ||||||
m.reg = prometheus.NewRegistry() | ||||||
} | ||||||
|
||||||
if *profilesPath != "" { | ||||||
finalize, err := snapshotProfiles(*profilesPath) | ||||||
if err != nil { | ||||||
level.Error(logger).Log("err", errors.Wrapf(err, "%s profiles init failed", cmd)) | ||||||
os.Exit(1) | ||||||
} | ||||||
defer logerrcapture.Do(logger, finalize, "profiles") | ||||||
} | ||||||
|
||||||
var g run.Group | ||||||
g.Add(func() error { | ||||||
// TODO(bwplotka): Move to customized better setup function. | ||||||
|
@@ -101,6 +133,65 @@ func main() { | |||||
level.Error(logger).Log("err", errors.Wrapf(err, "%s command failed", cmd)) | ||||||
os.Exit(1) | ||||||
} | ||||||
|
||||||
} | ||||||
|
||||||
func snapshotProfiles(dir string) (func() error, error) { | ||||||
now := time.Now().UTC() | ||||||
if err := os.MkdirAll(filepath.Join(dir, strings.ReplaceAll(now.Format(time.UnixDate), " ", "_")), os.ModePerm); err != nil { | ||||||
return nil, err | ||||||
} | ||||||
f, err := os.OpenFile(filepath.Join(dir, strings.ReplaceAll(now.Format(time.UnixDate), " ", "_"), "fgprof.pb.gz"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.ModePerm) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
|
||||||
m, err := os.OpenFile(filepath.Join(dir, strings.ReplaceAll(now.Format(time.UnixDate), " ", "_"), "memprof.pb.gz"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.ModePerm) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
runtime.GC() | ||||||
|
||||||
if err := pprof.WriteHeapProfile(m); err != nil { | ||||||
return nil, err | ||||||
} | ||||||
|
||||||
fgFunc := fgprof.Start(f, fgprof.FormatPprof) | ||||||
|
||||||
return func() (err error) { | ||||||
defer errcapture.Do(&err, f.Close, "close") | ||||||
return fgFunc() | ||||||
}, nil | ||||||
} | ||||||
|
||||||
func (m *mdoxMetrics) Print() error { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we printing here? (:: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you need struct.. just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||||||
mfs, err := m.reg.Gather() | ||||||
if err != nil { | ||||||
return err | ||||||
} | ||||||
now := time.Now().UTC() | ||||||
if err := os.MkdirAll(filepath.Join(m.dir, strings.ReplaceAll(now.Format(time.UnixDate), " ", "_")), os.ModePerm); err != nil { | ||||||
return err | ||||||
} | ||||||
f, err := os.OpenFile(filepath.Join(m.dir, strings.ReplaceAll(now.Format(time.UnixDate), " ", "_"), "metrics"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.ModePerm) | ||||||
if err != nil { | ||||||
return err | ||||||
} | ||||||
defer f.Close() | ||||||
|
||||||
for _, mf := range mfs { | ||||||
for _, metric := range mf.Metric { | ||||||
unixTime := now.Unix() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we cache now, so we have consistent timestamp across all series? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we do this already! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all good! |
||||||
metric.TimestampMs = &unixTime | ||||||
} | ||||||
if _, err := expfmt.MetricFamilyToOpenMetrics(f, mf); err != nil { | ||||||
return err | ||||||
} | ||||||
} | ||||||
if _, err = expfmt.FinalizeOpenMetrics(f); err != nil { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For future: Let's document how one can use those and import to Prometheus 🤗 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure! |
||||||
return err | ||||||
} | ||||||
return nil | ||||||
} | ||||||
|
||||||
func interrupt(logger log.Logger, cancel <-chan struct{}) error { | ||||||
|
@@ -115,7 +206,7 @@ func interrupt(logger log.Logger, cancel <-chan struct{}) error { | |||||
} | ||||||
} | ||||||
|
||||||
func registerFmt(_ context.Context, app *extkingpin.App) { | ||||||
func registerFmt(_ context.Context, app *extkingpin.App, m *mdoxMetrics) { | ||||||
cmd := app.Command("fmt", "Formats in-place given markdown files uniformly following GFM (Github Flavored Markdown: https://github.github.com/gfm/). Example: mdox fmt *.md") | ||||||
files := cmd.Arg("files", "Markdown file(s) to process.").Required().ExistingFiles() | ||||||
checkOnly := cmd.Flag("check", "If true, fmt will not modify the given files, instead it will fail if files needs formatting").Bool() | ||||||
|
@@ -131,6 +222,10 @@ This directive runs executable with arguments and put its stderr and stdout outp | |||||
linksValidateConfig := extflag.RegisterPathOrContent(cmd, "links.validate.config", "YAML file for skipping link check, with spec defined in github.com/bwplotka/mdox/pkg/linktransformer.ValidatorConfig", extflag.WithEnvSubstitution()) | ||||||
|
||||||
cmd.Run(func(ctx context.Context, logger log.Logger) (err error) { | ||||||
if m.reg != nil { | ||||||
defer logerrcapture.Do(logger, m.Print, "print") | ||||||
} | ||||||
|
||||||
var opts []mdformatter.Option | ||||||
if !*disableGenCodeBlocksDirectives { | ||||||
opts = append(opts, mdformatter.WithCodeBlockTransformer(mdgen.NewCodeBlockTransformer())) | ||||||
|
@@ -157,7 +252,7 @@ This directive runs executable with arguments and put its stderr and stdout outp | |||||
if err != nil { | ||||||
return err | ||||||
} | ||||||
v, err := linktransformer.NewValidator(ctx, logger, validateConfigContent, anchorDir) | ||||||
v, err := linktransformer.NewValidator(ctx, logger, validateConfigContent, anchorDir, m.reg) | ||||||
if err != nil { | ||||||
return err | ||||||
} | ||||||
|
@@ -172,7 +267,7 @@ This directive runs executable with arguments and put its stderr and stdout outp | |||||
} | ||||||
|
||||||
if *checkOnly { | ||||||
diff, err := mdformatter.IsFormatted(ctx, logger, *files, opts...) | ||||||
diff, err := mdformatter.IsFormatted(ctx, logger, *files, m.reg, opts...) | ||||||
if err != nil { | ||||||
return err | ||||||
} | ||||||
|
@@ -193,7 +288,7 @@ This directive runs executable with arguments and put its stderr and stdout outp | |||||
return errors.Errorf("files not formatted: %v", diffOut) | ||||||
|
||||||
} | ||||||
return mdformatter.Format(ctx, logger, *files, opts...) | ||||||
return mdformatter.Format(ctx, logger, *files, m.reg, opts...) | ||||||
}) | ||||||
} | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need extra struct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was kind of keeping the struct there since I was facing issues using just
reg
variable(metrics not registering due to how CLI operates). Will try out something else and see if I can remove.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've kept it for the
Print()
function now. 🙂