go.temporal.io/server/tools/tdbg/util.go
322 LOC · 68 covered · 254 uncovered · 23 ranges · 81 concepts · 13 introducers · 39 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
package tdbg
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"github.com/fatih/color"
"github.com/olekukonko/tablewriter"
"github.com/urfave/cli/v2"
"go.temporal.io/api/workflowservice/v1"
"go.temporal.io/server/chasm"
"go.temporal.io/server/common/codec"
"go.temporal.io/server/common/collection"
"go.temporal.io/server/common/namespace"
"google.golang.org/protobuf/proto"
)
var envKeysForUserName = []string{
"USER",
"LOGNAME",
"HOME",
}
for _, n := range envKeysForUserName {
if len(os.Getenv(n)) > 0 {
return os.Getenv(n)
}
}
return "unknown"
}
var b []byte
var err error
if pb, ok := o.(proto.Message); ok {
encoder := codec.NewJSONPBIndentEncoder(" ")
b, err = encoder.Encode(pb)
} else {
b, err = json.MarshalIndent(o, "", " ")
}
fmt.Fprintf(c.App.ErrWriter, "Error when trying to pretty-print: %v\n%v\n", err, o)
return
}
_, _ = c.App.Writer.Write([]byte("\n"))
}
value := c.String(optionName)
if len(value) == 0 {
return "", fmt.Errorf("option is required: %s", optionName)
}
}
func parseTime(timeStr string, defaultValue time.Time, now time.Time) (time.Time, error) {
if len(timeStr) == 0 {
return defaultValue, nil
}
// try to parse
parsedTime, err := time.Parse(defaultDateTimeFormat, timeStr)
if err == nil {
return parsedTime, nil
}
// treat as raw unix time
resultValue, err := strconv.ParseInt(timeStr, 10, 64)
if err == nil {
return time.Unix(0, resultValue).UTC(), nil
}
// treat as time range format
parsedTime, err = parseTimeRange(timeStr, now)
if err != nil {
return time.Time{}, fmt.Errorf("cannot parse time '%s', use UTC format '2006-01-02T15:04:05', "+
"time range or raw UnixNano directly. See help for more details: %s", timeStr, err)
}
return parsedTime, nil
}
// parseTimeRange parses a given time duration string (in format X<time-duration>) and
// returns parsed timestamp given that duration in the past from current time.
// All valid values must contain a number followed by a time-duration, from the following list (long form/short form):
// - second/s
// - minute/m
// - hour/h
// - day/d
// - week/w
// - month/M
// - year/y
// For example, possible input values, and their result:
// - "3d" or "3day" --> three days --> time.Now().UTC().Add(-3 * 24 * time.Hour)
// - "2m" or "2minute" --> two minutes --> time.Now().UTC().Add(-2 * time.Minute)
// - "1w" or "1week" --> one week --> time.Now().UTC().Add(-7 * 24 * time.Hour)
// - "30s" or "30second" --> thirty seconds --> time.Now().UTC().Add(-30 * time.Second)
// Note: Duration strings are case-sensitive, and should be used as mentioned above only.
// Limitation: Value of numerical multiplier, X should be in b/w 0 - 1e6 (1 million), boundary values excluded i.e.
// 0 < X < 1e6. Also, the maximum time in the past can be 1 January 1970 00:00:00 UTC (epoch time),
// so giving "1000y" will result in epoch time.
func parseTimeRange(timeRange string, now time.Time) (time.Time, error) {
match, err := regexp.MatchString(defaultDateTimeRangeShortRE, timeRange)
if !match { // fallback on to check if it's of longer notation
_, err = regexp.MatchString(defaultDateTimeRangeLongRE, timeRange)
}
if err != nil {
return time.Time{}, err
}
re, _ := regexp.Compile(defaultDateTimeRangeNum)
idx := re.FindStringSubmatchIndex(timeRange)
if idx == nil {
return time.Time{}, fmt.Errorf("cannot parse timeRange %s", timeRange)
}
num, err := strconv.Atoi(timeRange[idx[0]:idx[1]])
if err != nil {
return time.Time{}, fmt.Errorf("cannot parse timeRange %s", timeRange)
}
if num >= 1e6 {
return time.Time{}, fmt.Errorf("invalid time-duation multiplier %d, allowed range is 0 < multiplier < 1000000", num)
}
dur, err := parseTimeDuration(timeRange[idx[1]:])
if err != nil {
return time.Time{}, fmt.Errorf("cannot parse timeRange %s", timeRange)
}
res := now.Add(time.Duration(-num) * dur) // using server's local timezone
epochTime := time.Unix(0, 0).UTC()
if res.Before(epochTime) {
res = epochTime
}
return res, nil
}
// parseTimeDuration parses the given time duration in either short or long convention
// and returns the time.Duration
// Valid values (long notation/short notation):
// - second/s
// - minute/m
// - hour/h
// - day/d
// - week/w
// - month/M
// - year/y
// NOTE: the input "duration" is case-sensitive
func parseTimeDuration(duration string) (dur time.Duration, err error) {
switch duration {
case "s", "second":
dur = time.Second
case "m", "minute":
dur = time.Minute
case "h", "hour":
dur = time.Hour
case "d", "day":
dur = day
case "w", "week":
dur = week
case "M", "month":
dur = month
case "y", "year":
dur = year
default:
err = fmt.Errorf("unknown time duration %s", duration)
}
return
}
return newContextWithTimeout(c, defaultContextTimeout)
}
func newContextWithTimeout(c *cli.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
util.go ×3
if c.IsSet(FlagContextTimeout) {
timeout = time.Duration(c.Int(FlagContextTimeout)) * time.Second
}
}
if search == "" {
}
for key, value := range candidates {
}
}
return 0, fmt.Errorf("could not find corresponding candidate for %s. Possible candidates: %q", search, candidateNames)
util.go ×1
}
// paginate creates an interactive CLI mode to control the printing of items
func paginate[V any](c *cli.Context, paginationFn collection.PaginationFn[V], pageSize int) error {
more := c.Bool(FlagMore)
isTableView := !c.Bool(FlagPrintJSON)
iter := collection.NewPagingIterator(paginationFn)
var pageItems []any
for iter.HasNext() {
item, err := iter.Next()
if err != nil {
return err
}
pageItems = append(pageItems, item)
if len(pageItems) == pageSize || !iter.HasNext() {
if isTableView {
if err := printTable(pageItems, c.App.Writer); err != nil {
return err
}
} else {
prettyPrintJSONObject(c, pageItems)
}
if !more || !showNextPage(c.App.Writer) {
break
}
pageItems = pageItems[:0]
}
}
return nil
}
var exportRgx = regexp.MustCompile("^[A-Z]")
if len(items) == 0 {
}
for e.Type().Kind() == reflect.Pointer {
e = e.Elem()
}
var exportedFields []int
t := e.Type()
for i := 0; i < e.NumField(); i++ {
if exportRgx.MatchString(t.Field(i).Name) {
fields = append(fields, t.Field(i).Name)
exportedFields = append(exportedFields, i)
}
}
table.SetBorder(false)
table.SetColumnSeparator("|")
table.SetHeader(fields)
table.SetHeaderLine(false)
for i := range items {
item := reflect.ValueOf(items[i])
for item.Type().Kind() == reflect.Pointer {
item = item.Elem()
}
for _, j := range exportedFields {
col := item.Field(j)
columns = append(columns, fmt.Sprintf("%v", col.Interface()))
}
table.Append(columns)
}
table.ClearRows()
return nil
}
func showNextPage(wr io.Writer) bool {
fmt.Fprintf(wr, "Press %s to show next page, press %s to quit: ",
color.GreenString("Enter"), color.RedString("any other key then Enter"))
var input string
_, _ = fmt.Scanln(&input)
return strings.Trim(input, " ") == ""
}
func getNamespaceID(c *cli.Context, clientFactory ClientFactory, nsName namespace.Name) (namespace.ID, error) {
wfClient := clientFactory.WorkflowClient(c)
ctx, cancel := newContext(c)
defer cancel()
nsResponse, err := wfClient.DescribeNamespace(ctx, &workflowservice.DescribeNamespaceRequest{
Namespace: nsName.String(),
})
if err != nil {
return namespace.EmptyID, err
}
return namespace.ID(nsResponse.NamespaceInfo.GetId()), nil
}
func getArchetype(c *cli.Context) chasm.Archetype {
if c.IsSet(FlagArchetypeID) {
return ""
}
archetype := c.String(FlagArchetype)
if archetype != "" {
return archetype
}
return chasm.WorkflowArchetype
}