-
Notifications
You must be signed in to change notification settings - Fork 80
Add -p[1] performance statistics flag #625
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,6 +82,9 @@ type SQLCmdArguments struct { | |
| ChangePassword string | ||
| ChangePasswordAndExit string | ||
| TraceFile string | ||
| // PrintStatistics prints performance statistics after each batch | ||
| // nil = disabled, 0 = human-readable, 1 = colon-separated | ||
| PrintStatistics *int | ||
| // Keep Help at the end of the list | ||
| Help bool | ||
| } | ||
|
|
@@ -126,6 +129,7 @@ const ( | |
| disableCmdAndWarn = "disable-cmd-and-warn" | ||
| listServers = "list-servers" | ||
| removeControlCharacters = "remove-control-characters" | ||
| printStatistics = "print-statistics" | ||
| ) | ||
|
|
||
| func encryptConnectionAllowsTLS(value string) bool { | ||
|
|
@@ -330,6 +334,7 @@ func checkDefaultValue(args []string, i int) (val string) { | |
| 'k': "0", | ||
| 'L': "|", // | is the sentinel for no value since users are unlikely to use it. It's "reserved" in most shells | ||
| 'X': "0", | ||
| 'p': "0", | ||
| } | ||
| if isFlag(args[i]) && len(args[i]) == 2 && (len(args) == i+1 || args[i+1][0] == '-') { | ||
| if v, ok := flags[rune(args[i][1])]; ok { | ||
|
|
@@ -393,6 +398,7 @@ func SetScreenWidthFlags(args *SQLCmdArguments, rootCmd *cobra.Command) { | |
| args.DisableCmd = getOptionalIntArgument(rootCmd, disableCmdAndWarn) | ||
| args.ErrorsToStderr = getOptionalIntArgument(rootCmd, errorsToStderr) | ||
| args.RemoveControlCharacters = getOptionalIntArgument(rootCmd, removeControlCharacters) | ||
| args.PrintStatistics = getOptionalIntArgument(rootCmd, printStatistics) | ||
| } | ||
|
|
||
| func setFlags(rootCmd *cobra.Command, args *SQLCmdArguments) { | ||
|
|
@@ -474,6 +480,7 @@ func setFlags(rootCmd *cobra.Command, args *SQLCmdArguments) { | |
| _ = rootCmd.Flags().BoolP("enable-quoted-identifiers", "I", true, localizer.Sprintf("Provided for backward compatibility. Quoted identifiers are always enabled")) | ||
| _ = rootCmd.Flags().BoolP("client-regional-setting", "R", false, localizer.Sprintf("Provided for backward compatibility. Client regional settings are not used")) | ||
| _ = rootCmd.Flags().IntP(removeControlCharacters, "k", 0, localizer.Sprintf("%s Remove control characters from output. Pass 1 to substitute a space per character, 2 for a space per consecutive characters", "-k [1|2]")) | ||
| _ = rootCmd.Flags().IntP(printStatistics, "p", -1, localizer.Sprintf("%s Print performance statistics for every result set. Pass 1 to output in colon-separated format", "-p[1]")) | ||
|
||
| rootCmd.Flags().BoolVarP(&args.EchoInput, "echo-input", "e", false, localizer.Sprintf("Echo input")) | ||
| rootCmd.Flags().IntVarP(&args.QueryTimeout, "query-timeout", "t", 0, "Query timeout") | ||
| rootCmd.Flags().BoolVarP(&args.EnableColumnEncryption, "enable-column-encryption", "g", false, localizer.Sprintf("Enable column encryption")) | ||
|
|
@@ -543,6 +550,14 @@ func normalizeFlags(cmd *cobra.Command) error { | |
| err = invalidParameterError("-k", v, "1", "2") | ||
| return pflag.NormalizedName("") | ||
| } | ||
| case printStatistics: | ||
| switch v { | ||
| case "0", "1": | ||
| return pflag.NormalizedName(name) | ||
| default: | ||
| err = invalidParameterError("-p", v, "0", "1") | ||
| return pflag.NormalizedName("") | ||
| } | ||
| } | ||
|
|
||
| return pflag.NormalizedName(name) | ||
|
|
@@ -829,6 +844,7 @@ func run(vars *sqlcmd.Variables, args *SQLCmdArguments) (int, error) { | |
|
|
||
| s.Connect = &connectConfig | ||
| s.Format = sqlcmd.NewSQLCmdDefaultFormatter(args.TrimSpaces, args.getControlCharacterBehavior()) | ||
| s.PrintStatistics = args.PrintStatistics | ||
| if args.OutputFile != "" { | ||
| err = s.RunCommand(s.Cmd["OUT"], []string{args.OutputFile}) | ||
| if err != nil { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -88,6 +88,17 @@ type Sqlcmd struct { | |
| EchoInput bool | ||
| colorizer color.Colorizer | ||
| termchan chan os.Signal | ||
| // PrintStatistics controls whether performance statistics are printed after each batch | ||
| // nil = disabled, 0 = human-readable format, 1 = colon-separated format | ||
| PrintStatistics *int | ||
| // stats tracks cumulative statistics for the session | ||
| stats *SessionStats | ||
| } | ||
|
|
||
| // SessionStats tracks cumulative performance statistics for a sqlcmd session | ||
| type SessionStats struct { | ||
| TotalTransactions int | ||
| TotalTimeMs float64 | ||
| } | ||
|
|
||
| // New creates a new Sqlcmd instance. | ||
|
|
@@ -420,6 +431,9 @@ func (s *Sqlcmd) getRunnableQuery(q string) string { | |
| // -101: No rows found | ||
| // -102: Conversion error occurred when selecting return value | ||
| func (s *Sqlcmd) runQuery(query string) (int, error) { | ||
| // Start timing for statistics | ||
| startTime := time.Now() | ||
|
|
||
| retcode := -101 | ||
| s.Format.BeginBatch(query, s.vars, s.GetOutput(), s.GetError()) | ||
| ctx := context.Background() | ||
|
|
@@ -508,6 +522,13 @@ func (s *Sqlcmd) runQuery(query string) (int, error) { | |
| } | ||
| } | ||
| s.Format.EndBatch() | ||
|
|
||
| // Print statistics if enabled | ||
| if s.PrintStatistics != nil { | ||
| elapsed := time.Since(startTime) | ||
| s.printStatistics(elapsed) | ||
| } | ||
|
|
||
| return retcode, qe | ||
| } | ||
|
|
||
|
|
@@ -552,6 +573,48 @@ func (s *Sqlcmd) handleError(retcode *int, err error) error { | |
| return nil | ||
| } | ||
|
|
||
| // printStatistics prints performance statistics for the query | ||
| func (s *Sqlcmd) printStatistics(elapsed time.Duration) { | ||
| if s.stats == nil { | ||
| s.stats = &SessionStats{} | ||
| } | ||
|
|
||
| // Update cumulative statistics | ||
| s.stats.TotalTransactions++ | ||
| elapsedMs := float64(elapsed.Milliseconds()) | ||
| s.stats.TotalTimeMs += elapsedMs | ||
|
|
||
| // Calculate statistics | ||
| avgMs := s.stats.TotalTimeMs / float64(s.stats.TotalTransactions) | ||
| var xactsPerSec float64 | ||
| if s.stats.TotalTimeMs > 0 { | ||
| xactsPerSec = float64(s.stats.TotalTransactions) / (s.stats.TotalTimeMs / 1000.0) | ||
| } | ||
|
|
||
| // Get packet size from connection settings | ||
| packetSize := s.Connect.PacketSize | ||
| if packetSize == 0 { | ||
| packetSize = 4096 // default | ||
| } | ||
|
|
||
| out := s.GetOutput() | ||
|
|
||
| if *s.PrintStatistics == 1 { | ||
| // Colon-separated format for spreadsheet/script processing | ||
| _, _ = out.Write([]byte(localizer.Sprintf("Network packet size (bytes):%d%s", packetSize, SqlcmdEol))) | ||
| _, _ = out.Write([]byte(localizer.Sprintf("%d xact(s):%s", s.stats.TotalTransactions, SqlcmdEol))) | ||
| _, _ = out.Write([]byte(localizer.Sprintf("Clock Time (ms.): total:%d:avg:%.2f:(%.4f xacts per sec.)%s", | ||
| int(s.stats.TotalTimeMs), avgMs, xactsPerSec, SqlcmdEol))) | ||
| } else { | ||
| // Human-readable format | ||
| _, _ = out.Write([]byte(SqlcmdEol)) | ||
| _, _ = out.Write([]byte(localizer.Sprintf("Network packet size (bytes): %d%s", packetSize, SqlcmdEol))) | ||
| _, _ = out.Write([]byte(localizer.Sprintf("%d xact(s):%s", s.stats.TotalTransactions, SqlcmdEol))) | ||
| _, _ = out.Write([]byte(localizer.Sprintf("Clock Time (ms.): total %d avg %.2f (%.4f xacts per sec.)%s", | ||
| int(s.stats.TotalTimeMs), avgMs, xactsPerSec, SqlcmdEol))) | ||
|
Comment on lines
+606
to
+614
|
||
| } | ||
| } | ||
|
|
||
| // Log attempts to write driver traces to the current output. It ignores errors | ||
| func (s Sqlcmd) Log(_ context.Context, _ msdsn.Log, msg string) { | ||
| _, _ = s.GetOutput().Write([]byte("DRIVER:" + msg)) | ||
|
|
||
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.
The documentation states the flag "prints performance statistics after each result set" but the implementation prints statistics after each batch (GO command), not after each individual result set within a batch. Consider updating the documentation to say "after each batch" to accurately reflect the implementation behavior.