Simple profiling for Go

Related tags

Performance profile
Overview

profile

Simple profiling support package for Go

Build Status GoDoc

installation

go get github.com/pkg/profile

usage

Enabling profiling in your application is as simple as one line at the top of your main function

import "github.com/pkg/profile"

func main() {
    defer profile.Start().Stop()
    ...
}

options

What to profile is controlled by config value passed to profile.Start. By default CPU profiling is enabled.

import "github.com/pkg/profile"

func main() {
    // p.Stop() must be called before the program exits to
    // ensure profiling information is written to disk.
    p := profile.Start(profile.MemProfile, profile.ProfilePath("."), profile.NoShutdownHook)
    ...
    // You can enable different kinds of memory profiling, either Heap or Allocs where Heap
    // profiling is the default with profile.MemProfile.
    p := profile.Start(profile.MemProfileAllocs, profile.ProfilePath("."), profile.NoShutdownHook)
}

Several convenience package level values are provided for cpu, memory, and block (contention) profiling.

For more complex options, consult the documentation.

contributing

We welcome pull requests, bug fixes and issue reports.

Before proposing a change, please discuss it first by raising an issue.

Issues
  • Proposal

    Proposal

    Because you liked my first suggestion, I took the liberty of suggesting some more changes to simplify readability. Hope they are welcome and I'd love to hear what you think of them. I also hope I didn't break anything :100:

    opened by gbbr 14
  • Multi-modal profiling

    Multi-modal profiling

    Problem Statement

    From my viewpoint, the biggest thing stopping me from using github.com/pkg/profile is its hard stance on "no more than one kind of profiling at a time". Especially since my usual starting assumption is "show me both a CPU profile and a memory (allocs) profile".

    Taking a CPU + memory profile, or a CPU + memory + goroutines + mutexes profile is actually quite a sane thing to do in my viewpoint. Further I do sometimes want to get both a trace and a cpu profile from the same long-running program, rather than go to all the trouble of running it twice.

    So with that introduction, let me lay out my understanding of why this is an okay thing to do (conversely, why the restriction on "one at a time" is actually necessary and overly restrictive).

    Kinds of Profiling

    For the sake of my argument I'm going to focus primarily on the distinction between active and passive profiles.

    There are two kinds of active profiles: CPU profiling and execution tracing. Here the cost of instrumentation is so high that they only run for a set amount of time (say take a 10-second CPU profile from a server, or re-running a tool with trace profiling turned on for the duration).

    On the other hand there are passive profiles including allocs, block, goroutine, heap, mutex, and threadcreate. Here the instrumentation cost is low enough that these profiles are latently always on; their counts are collected all the time, and can be collected at any point in time.

    NOTE this model is of course complicated by the fact that there is a tuning knob for the passive/always-on memory profiler, which pkg/profile is fairly unique in making such a user-facing API-out of (to say nothing of the basically unusable CPU profiler hz tuning knob...)

    Common Combinations

    Of all those lovely modes of profiling CPU, memory, and tracing are the most generally known, used, and useful; as evidenced by the prevalent flag idioms -cpuprofile FILE, -memprofile FILE, and -trace FILE.

    There's really no problem in combining CPU profiling and memory profiling (at normal rates); to the contrary: since the memory profiler is always on any how, you may as well dump it at the end (in the context of an offline tool, which seems to be the use-case that pkg/profile is most suited for). The same goes for any/all of the other passive profilers: they already have their counts just sitting there, you're only doing yourself a harm by not dumping the data.

    The most concern comes when combining CPU profiling and tracing. But even there, at least in my experience, any cross-chatter is fairly easy to pick out or compensate for:

    • in the event trace, you can at least see that a CPU profile was going on, since there will be at least one dedicated goroutine writing it out over some time span (you should even be able to see the os-interrupt timing... but I've not actually tried to do that, and now I'm purely speculating in parenthetical...)
    • there is real concern when it comes to skewing the CPU profile however, since every trace instrumentation branch is now hot, further inflating (the probably already dominant) impact of runtime functions
    • in practice, for online servers, I always sequence tracing and CPU profiling for this reason, rather than do them in parallel; however for an offline tool where your profiling its entire lifecycle, there are times when you want to see both concurrently (even if you also re-run it to also get a "pure" trace and cpu profile)

    Why I Care

    In my view pkg/profile is very close to fully solving the use case of "lifetime profiling for an offline tool / batch task". I'd like to wrap some flag.Value implementation around it and use it as a dependency (maybe even send a pull request for adding the flag convenience).

    However in its current form, not being able to take several at once is a bit of a blocker for that use case.

    opened by jcorbin 11
  • parsing profile: unrecognized profile format under MacOs Sierra 10.12.5

    parsing profile: unrecognized profile format under MacOs Sierra 10.12.5

    Hi, I'm trying to profile my application using your package following the documentation. I have a cpu.pprof non empty file generated but pprof is unable to read it

    $ go tool pprof —pdf ./app /var/folders/x7/wnp2zjn563j2dr45dsfpvdz80000gn/T/profile464041113/cpu.pprof > out.pdf              [email protected]
    parsing profile: unrecognized profile format
    

    I've tried multiple configuration from the default one defer profile.Start().Stop() to a more elaborated defer profile.Start(profile.BlockProfile, profile.MemProfile, profile.CPUProfile).Stop() but pprof is still unable to parse it. Any ideas?

    FYI

    $ go version                                                                                                                  
    go version go1.8.3 darwin/amd64
    

    Thanks

    opened by nsitbon 11
  • Empty CPU Profile

    Empty CPU Profile

    I'm trying to profile a simple application:

    package main
    
    import (
        "github.com/pkg/profile"
        "github.com/spf13/cobra"
        "strings"
    )
    
    /*
    Tokenize TODO
    */
    func Tokenize(text string) []string {
        return strings.Split(text, "\n")
    }
    
    func main() {
        profileCommand := &cobra.Command{
            Use: "profile",
            Run: func(cmd *cobra.Command, args []string) {
                defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop()
    
                Tokenize("Hello\nWorld")
            },
        }
    
        rootCommand := &cobra.Command{Use: "bern"}
        rootCommand.AddCommand(profileCommand)
        rootCommand.Execute()
    }
    

    But when I look at the cpu.pprof file it simply contains ' and nothing else. What am I doing incorrectly?

    opened by ianwalter 8
  • CLI flags

    CLI flags

    Adds command-line flags. The same flag names as the test tool are being used for overall consistency and feel.

    Updates #12 - CLI Flags

    opened by gbbr 8
  • profile.Start() should only be called once

    profile.Start() should only be called once

    Now that we've made it impossible to activate more than one profile at a time, users may attempt to work around this with something like

     defer profile.Start(profile.CPUProfile).Stop
     defer profile.Start(profile.MemProfile).Stop
    

    We should decide to support this, and if so, make sure that we move anything which is shared across profiles into the *Profile, see #5. Or, we should prohibit it with some package global lock.

    opened by davecheney 7
  • 10 byte profile file can't be opened

    10 byte profile file can't be opened

    After profiling I get a 10 byte file (cpu.pprof.zip) that when read produces:

    • cpu.pprof: decompressing profile: unexpected EOF ( With go tool pprof cpu.pprof)
    • Unexpected end of ZLIB input stream (With Goland)

    Tried both:

    defer profile.Start(profile.ProfilePath(os.Getenv("HOME"))).Stop()
    

    as well as:

    p := profile.Start(profile.ProfilePath(os.Getenv("HOME")), profile.NoShutdownHook)
    // ...
    p.Stop()
    

    As of 3704c8d23324a3fc0771ad2c1cef0aa4e9459f6f on macOS Big Sur 11.1.

    With the shutdown hook I saw the library was intercepting shutdown to stop profiling. Without it I see my code called to .Stop().

    Troubleshooting ideas?

    Thanks for your work.

    opened by thnk2wn 7
  • Switched to switch

    Switched to switch

    I was looking over this with the hope to learn something new and I found it was a great example of using the functional options you spoke about during your talk!

    I thought I'd make this change because I personally find it more readable to use a switch when there are multiple if's, it kind of prepares the reader for a lot of conditions. The fact that Go also allows us to have a "no parameter" switch gives us the opportunity to do such things.

    This is obviously just a personal opinion and you don't have to merge it if you don't agree.

    opened by gbbr 6
  • Bug: Interrupt Stop:  Race fix during Interrupt.

    Bug: Interrupt Stop: Race fix during Interrupt.

    The issue is described here https://github.com/minio/minio/issues/1617 . The proposed fix solves the issue.

    opened by hackintoshrao 5
  • Move the CI to Github Actions

    Move the CI to Github Actions

    Travis-ci.org is ceased. I just did the migration to GH actions for two of my repos so when I saw this I thought if you also need to move this can help and took me just few minutes.

    Ignore if you have other plans.

    opened by kavehmz 0
  • Doesn't releases the file even after calling profile.Stop()

    Doesn't releases the file even after calling profile.Stop()

    Failed to remove the files created by the profile pkg "error":"remove mem.pprof: The process cannot access the file because it is being used by another process."

    opened by prakashettigar 1
  • Named interface for Stop()

    Named interface for Stop()

    Problem

    Currently the signature of the Start function is

    func Start(options ...func(*Profile)) interface {
    	Stop()
    } 
    

    Use case

    The lifecycle in our case is managed by another library (fluent-bit-go)

    • The Start() has to happen in lifecycle function FLBPluginInit
    • The Stop() has to happen in lifecycle function FLBPluginExit

    Proposal

    have a named interface for Stop

    type Stopper interface {
    	Stop()
    } 
    

    So the Start will become

    func Start(options ...func(*Profile)) Stopper
    
    opened by kishaningithub 10
  • Timing profiling?

    Timing profiling?

    Hi. I hope everyone is having a great new year so far.

    I was told by a colleague I could use this package for determining how much time is being spent in each function during an application's run. Is this correct and, if so, how? I saw nothing in the documentation which makes this clear if so.

    Thanks in advance.

    opened by FrankDMartinez 7
  • added power support arch ppc64le on yml file.

    added power support arch ppc64le on yml file.

    Hi Team, Added power support for the travis.yml file with ppc64le. This is part of the Ubuntu distribution for ppc64le. This helps us simplify testing later when distributions are re-building and re-releasing. For more info tag @gerrith3. continuous integration build has passed after adding power support arch ppc64le without any failures. please check and close this component.

    opened by srinivas32 0
  • Add ProfileFilename

    Add ProfileFilename

    Every time I use this package, I think that I can do something like:

    var flagCPUProfile = flag.String("cpuprofile", "", "write a cpuprofile to `cpuprofile`")
    
    func main() {
    	if *flagCPUProfile != "" {
    		defer profile.Start(profile.ProfilePath(*flagCPUProfile)).Stop()
    	}
    

    And then I run the program with -cpuprofile=somename.pprof. And there is no profile to be found.

    And then I dig through the docs and discover ProfilePath is supposed to be a directory. And then I wish there was a way to provide a filename. This is because I sometimes do multiple runs, and I want to write the results to different profiles so that I can combine them, and I don't want to have to deal with creating and cleaning up a directory per profile.

    May I send a PR to add ProfileFilename, or something like it?

    opened by josharian 2
  • Source of memory increase

    Source of memory increase

    I've written a program that reads a csv (2.1m records) and builds sql insert statements into mariadb instance. It takes about 7minutes to complete the cpu level on my macbook pro (sierra) go to about 65% and the activity monitor shows upwards of 1GB to even 3.7 GB sometimes. I added the profiler to my project and tried to analyze the results but it seems to show that the program used about 25MB. I'm not sure what to read from this. here is a contrived version of my code and some pictures of the activity monitor.

    func main(){
        //Setup and Connect to database 
        //Set variables
        //Read CSV File
        dbConnect()
        defer db.Close()
        db.DB().SetMaxOpenConns(90)
        db.DB().SetMaxIdleConns(10)
        db.DB().SetConnMaxLifetime(time.Second * 14400)
        filehandle, err := os.Open(physicianCSV)
        checkErr(err)
        defer filehandle.Close()
    
        reader := csv.NewReader(filehandle)
        _, err = reader.Read()
        checkErr(err)
    
        for i := 0; i <= readLimit; i++ {
            record, err := reader.Read()
            if err != nil {
                if err == io.EOF {
                    break
                }
                panic(err)
            }
            physician = convertCSVRecordToPhysician(record)
            //..Do stuff with physician, edit object properties via pointer
    
            physicians = append(physicians, physician)
    
            if math.Mod(float64(i), float64(bulkAmount)) == 0 && i != 0 {
                fmt.Println(i, "Records: From ", i-bulkAmount, "to", i)
                wg.Add(1)
                sliceOfPhys := make([]Physician, bulkAmount)
                copy(sliceOfPhys, physicians)
                go bulkSavePhysicians(sliceOfPhys)
                physicians = physicians[:0]
    
            }
        }
    
        fmt.Println(readLimit, "records inserted")
        wg.Wait()
    }
    
    func bulkSavePhysicians(_physicians []Physician) {
        defer func() {
            if x := recover(); x != nil {
                fmt.Println(x)
            }
        }()
        defer wg.Done()
    
        sqlStringArray := buildSQLStatements(_physicians)
        batchSQL := fmt.Sprintf("insert into physicians values %s ;", strings.Join(sqlStringArray, ","))
        tx := db.Begin()
        errors := tx.Exec(batchSQL).GetErrors()
        if len(errors) > 0 {
            panic(errors)
        }
        tx.Commit()
    }
    
    func buildSQLStatements(_physicians []Physician) []string {
    
        var valueStr string
        var valueArr []string
        for _, phys := range _physicians {
    
            valueStr = fmt.Sprintf(`( "%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s" )`, phys.NPI,
                phys.PACID,
                phys.ProfessionalEnrollmentID,
                strings.Replace(phys.LastName, "'", "\\'", -1),
                phys.FirstName,
                phys.MiddleName,
                phys.Suffix,
                phys.Gender,
                phys.Credential,
                strings.Replace(phys.MedicalSchoolName, "'", "\\'", -1),
                phys.GraduationYear,
                phys.PrimarySpecialty,
                phys.SecondarySpecialty1,
                phys.SecondarySpecialty2,
                phys.SecondarySpecialty3,
                phys.SecondarySpecialty4,
                phys.AllSecondarySpecialties,
                strings.Replace(phys.OrganizationLegalName, "'", "\\'", -1),
                phys.GroupPracticePACID,
                phys.NumberOfGroupPracticeMembers,
                strings.Replace(phys.Line1StreetAddress, "'", "\\'", -1),
                phys.Line2StreetAddress,
                phys.MarkerOfAddressLine2Suppression,
                phys.City,
                phys.State,
                phys.ZipCode,
                phys.PhoneNumber,
                phys.HospitalAffiliationCCN1,
                phys.HospitalAffiliationLBN1,
                phys.HospitalAffiliationCCN2,
                phys.HospitalAffiliationLBN2,
                phys.HospitalAffiliationCCN3,
                phys.HospitalAffiliationLBN3,
                phys.HospitalAffiliationCCN4,
                phys.HospitalAffiliationLBN4,
                phys.HospitalAffiliationCCN5,
                phys.HospitalAffiliationLBN5,
                phys.ProfessionalAcceptsMedicareAssignment,
                phys.ReportedQualityMeasures,
                phys.UsedElectronicHealthRecords,
                phys.ParticipatedInTheMedicareMaintenance,
                phys.CommittedToHeartHealth,
                phys.SpecialtyID)
    
            valueArr = append(valueArr, valueStr)
        }
        return valueArr
    }
    

    Here is zip of mem.pprof and cpu.pprof proffs.zip

    An image of activity monitor taken without the profiler inclusion. 36999fa9-22bc-42d6-99e0-056200051aea

    I watched your talk and you mentioned that heapSys is the correct value to indicate the amount of memory used in bytes. Am I correct in saying this? 203751424 bytes = 203MB Calling web from go tool pprof mem.pprof renders the following image

    I'm confused as to which values to trust.

    opened by beatscode 0
  • Add NoProfile

    Add NoProfile

    For programs that have both non-trivial shutdown hook plumbing and flags controlling profiling, it'd be convenient to have a NoProfile option with a no-op Stop function. I'll send a PR if you're amenable.

    opened by josharian 3
  • Support writing to arbitrary io.Writer

    Support writing to arbitrary io.Writer

    Hi,

    I have a use case where I want to trigger profiles on a remote server and then load the resulting file via an HTTP endpoint. It seemed to me supporting writing the raw bytes to an arbitrary io.Writer would be the most generic way of supporting that use case (and potentially others).

    I have a fork with code that works for me on master at https://github.com/VerizonDigital/profile, but it's kind of messy feeling and doesn't have any new tests for the io.Writer portion. Is this something you're interested in? I'm more than happy to change the way I've implemented it if you have any changes you'd like made.

    Thanks for the work you've done so far. Starting with this made my life easier. :D

    opened by jcline 2
Releases(v1.6.0)
  • v1.5.0(May 20, 2020)

  • v1.4.0(Nov 21, 2019)

  • v1.2.1(May 10, 2017)

    New features

    • Version 1.2.1 adds support for the Go mutex profile. Thanks @valyala

    Bug fixes

    • Version 1.2.1 also fixes a bug executing tests in some environments where README.md had been removed. Fixes #40
    • Missing Stop() call in ExampleProfilePath was fixed. Thanks @mark-rushakoff
    Source code(tar.gz)
    Source code(zip)
  • v1.2.0(Aug 1, 2016)

    Version 1.2.0 adds support for the Go execution tracer. This feature was added to Go 1.5 but hasn't really been usable til Go 1.7. Support for profile.Start(profile.TraceProfile) is only available in Go 1.7 and later.

    Source code(tar.gz)
    Source code(zip)
  • v1.1.0(Jun 1, 2016)

    Version 1.1.0 adds explicit support for stopping and starting a profile multiple times. This was previously support, but untested, and contained a race condition. The race has been fixed and profiling multiple times during the execution of a program is now supported. Thanks to @valyala for adding this feature.

    Fixes #18, #19

    Source code(tar.gz)
    Source code(zip)
Owner
Artisanal, hand crafted, barrel aged, Go packages
null
Simple profiling for Go

profile Simple profiling support package for Go installation go get github.com/pkg/profile usage Enabling profiling in your application is as simple

null 1.6k Dec 5, 2021
Simple profiling for Go

profile Simple profiling for Go. Easy management of Go's built-in profiling and tracing Based on the widely-used pkg/profile: mostly-compatible API Su

Michael McLoughlin 68 Nov 22, 2021
🔥 Continuous profiling platform — debug performance issues in your code!

Pyroscope is an open source continuous profiling platform.

Pyroscope 4.7k Dec 3, 2021
A GNU/Linux monitoring and profiling tool focused on single processes.

Uroboros is a GNU/Linux monitoring tool focused on single processes. While utilities like top, ps and htop provide great overall details, they often l

Simone Margaritelli 637 Dec 5, 2021
pprof is a tool for visualization and analysis of profiling data

Introduction pprof is a tool for visualization and analysis of profiling data. pprof reads a collection of profiling samples in profile.proto format a

Google 4.9k Nov 30, 2021
perfessor - Continuous Profiling Sidecar

perfessor - Continuous Profiling Sidecar About Perfessor is a continuous profiling agent that can profile running programs using perf It then converts

null 50 Nov 22, 2021
Continuous profiling for long-term postmortem analysis

profefe, a continuous profiling system, collects profiling data from a fleet of running applications and provides API for querying profiling samples for postmortem performance analysis.

profefe - continuous profiling 507 Nov 25, 2021
A tool suite for Redis profiling

Insecticide Insecticide is a tool suite for Redis profiling. It finds ambiguous values in your redis configuration.

Сити-Мобил 8 Sep 6, 2021
conprof - Continuous Profiling

conprof - Continuous Profiling Conprof is a continuous profiling project. Continuous profiling is the act of taking profiles of programs in a systemat

Parca 3 Nov 10, 2021
Continuous profiling for analysis of CPU, memory usage over time, and down to the line number. Saving infrastructure cost, improving performance, and increasing reliability.

Continuous profiling for analysis of CPU, memory usage over time, and down to the line number. Saving infrastructure cost, improving performance, and increasing reliability.

Parca 1.5k Dec 4, 2021
A profiling tool to peek and profile the memory or cpu usage of a process

Peekprof Get the CPU and Memory usage of a single process, monitor it live, and extract it in CSV and HTML. Get the best out of your optimizations. Us

Apostolis A. 5 Oct 10, 2021
simple-jwt-provider - Simple and lightweight provider which exhibits JWTs, supports login, password-reset (via mail) and user management.

Simple and lightweight JWT-Provider written in go (golang). It exhibits JWT for the in postgres persisted user, which can be managed via api. Also, a password-reset flow via mail verification is available. User specific custom-claims also available for jwt-generation and mail rendering.

Max 20 Dec 1, 2021
Simple Bank is a simple REST API that allows users to perform transferences with each other.

Simple Bank is a simple REST API that allows users to perform transferences with each other. ?? Technologies Golang Docker PostgreSQ

Matheus Mosca 10 Sep 18, 2021
bf.go - A dead simple brainfuck interpreter Slow and simple

bf.go - A dead simple brainfuck interpreter Slow and simple. Can execute pretty much all tested Brainfuck scripts. Installation If you have Go install

Chris 0 Oct 15, 2021
A simple project (which is visitor counter) on kubernetesA simple project (which is visitor counter) on kubernetes

k8s playground This project aims to deploy a simple project (which is visitor counter) on kubernetes. Deploy steps kubectl apply -f secret.yaml kubect

null 6 Nov 23, 2021
A simple http-web server logging incoming requests to stdout with simple http-interface.

http-cli-echo-logger A simple http-web server logging incoming requests to stdout with simple http-interface. Run locally go run ./cmd/main.go Default

Andrii Bosonchenko 1 Nov 20, 2021
Simple to do list API with Gin and Gorm (with Postgres)Simple to do list API with Gin and Gorm (with Postgres)

go-todo Simple to do list API with Gin and Gorm (with Postgres) Docker Clone this repository and run: docker-compose up You can then hit the followin

ansh-dev 0 Nov 20, 2021
go-simple-geo is a library for simple geo calculations.

go-simple-geo is a library for simple geo calculations. Installation go get github.com/EpicStep/go-simple-geo Example package main import ( "github.

Stepan Rabotkin 1 Dec 6, 2021
Go-Guardian is a golang library that provides a simple, clean, and idiomatic way to create powerful modern API and web authentication.

❗ Cache package has been moved to libcache repository Go-Guardian Go-Guardian is a golang library that provides a simple, clean, and idiomatic way to

Sanad Haj Yahya 322 Nov 30, 2021
Package goth provides a simple, clean, and idiomatic way to write authentication packages for Go web applications.

Goth: Multi-Provider Authentication for Go Package goth provides a simple, clean, and idiomatic way to write authentication packages for Go web applic

Mark Bates 3.4k Dec 5, 2021
Safe, simple and fast JSON Web Tokens for Go

jwt JSON Web Token for Go RFC 7519, also see jwt.io for more. The latest version is v3. Rationale There are many JWT libraries, but many of them are h

cristaltech 307 Nov 30, 2021
A dead simple, highly performant, highly customizable sessions middleware for go http servers.

If you're interested in jwt's, see my jwt library! Sessions A dead simple, highly performant, highly customizable sessions service for go http servers

Adam Hanna 60 Jul 3, 2021
Simple JWT Golang

sjwt Simple JSON Web Token - Uses HMAC SHA-256 Example // Set Claims claims := New() claims.Set("username", "billymister") claims.Set("account_id", 86

Brian Voelker 94 Sep 22, 2021
Simple yet customizable bot framework written in Go.

Introduction Sarah is a general-purpose bot framework named after the author's firstborn daughter. This comes with a unique feature called "stateful c

Go Hagiwara 212 Dec 4, 2021
Concurrent task runner, developer's routine tasks automation toolkit. Simple modern alternative to GNU Make 🧰

taskctl - concurrent task runner, developer's routine tasks automation toolkit Simple modern alternative to GNU Make. taskctl is concurrent task runne

null 144 Nov 24, 2021
Simple and complete API for building command line applications in Go

Simple and complete API for building command line applications in Go Module cli provides a simple, fast and complete API for building command line app

teris.io 96 Nov 24, 2021
A Simple and Clear CLI library. Dependency free.

A Simple and Clear CLI library. Dependency free. Features Nested Subcommands Uses the standard library flag package Auto-generated help Custom banners

Lea Anthony 78 Dec 1, 2021
A simple command line notebook for programmers

Dnote is a simple command line notebook for programmers. It keeps you focused by providing a way of effortlessly capturing and retrieving information

Dnote 2.2k Nov 25, 2021
Flag is a simple but powerful command line option parsing library for Go support infinite level subcommand

Flag Flag is a simple but powerful commandline flag parsing library for Go. Documentation Documentation can be found at Godoc Supported features bool

null 117 Nov 2, 2021