按格式不一致的经过时间字段排序(K8S 事件按事件发生后的实际时间排序)

sorting by inconsistently formatted elapsed time field ( k8s events by actual time since event )

提问人:Paul Hodges 提问时间:12/14/2022 最后编辑:brian d foyPaul Hodges 更新时间:12/15/2022 访问量:140

问:

编辑

我发现自己经常在寻找一些事情是否停止了发生。为此,按时间顺序查看事件会有所帮助......

这个解决方案似乎有效,但格式仍然让我发疯......

我“有点”有效的解决方案——

kubectl get events |
  sed -E '/^[6789][0-9]s/{h; s/^(.).*/\1/; y/6789/0123/; s/^(.)/01m\1/;
                          x; s/^.(.*)/\1/; H;
                          x; s/\n//; };
          s/^10([0-9]s)/01m4\1/; s/^11([0-9]s)/01m5\1/; s/^([0-9]s)/00m0\1/; s/^([0-9]+s)/00m\1/;
          s/^([0-9]m)/0\1/; s/^([0-9]+m)([0-9]s)/\10\2/;
          s/^L/_L/;' | sort -r

...这对我来说似乎有点矫枉过正。

以空格分隔的左对齐字段没有前导零,仅报告最多 2m 的秒数,然后报告最多 5m,之后似乎只报告。[0-9]+s[0-9]+m[0-9]+s[0-9]+m

有人有一个简短的,甚至可能很简单,更容易阅读的解决方案吗?
没有工具的偏好(、、、本机等),只要它有效并且可能已经安装在我需要工作的任何地方。
sedawkperlbash

这不是一个高优先级,但似乎是一个有趣的小挑战,我想我会分享。

我的测试数据:

$: cat sample
LAST ...
28s ...
2m22s ...
46m ...
7s ...
75s ...
119s ...

具有所需输出的结果 -

$: sed -E '/^[6789][0-9]s/{h; s/^(.).*/\1/; y/6789/0123/; s/^(.)/01m\1/;
                           x; s/^.(.*)/\1/; H;
                           x; s/\n//; };
           s/^10([0-9]s)/01m4\1/; s/^11([0-9]s)/01m5\1/; s/^([0-9]s)/00m0\1/; s/^([0-9]+s)/00m\1/;
           s/^([0-9]m)/0\1/; s/^([0-9]+m)([0-9]s)/\10\2/;
           s/^L/_L/;' sample | sort -r
_LAST ...
46m ...
02m22s ...
01m59s ...
01m15s ...
00m28s ...
00m07s ...

我任意转换为现有通用输出格式的标准化版本,只是为了让它能够轻松传输给团队的其他成员。无论哪种方式,它都只是用于“盯着”数据,所以只要它易于阅读,其他格式就不是问题。

虽然理论上可以包括小时和天,但这种旧事件通常不会被这个工具报告,并且超出了这个问题的范围,如果需要,我可能会推断出提出的任何解决方案。由于我可以从这种方法中获得订单,因此我真的只是在寻找优雅的格式选项。

对 Daweo 解决方案的笨拙改编,带有格式 -awk

$: awk '/^[0-9]/{ if($1!~/m/){$1="0m" $1}; split($1,arr,/m/);
        t=arr[1]*60+arr[2]; m=(t-(t%60))/60; s=t-(m*60);
        m=sprintf("%02dm",m); if(s){ s=sprintf("%02ds",s) } else s="";
        $1=sprintf("%s%s",m,s); print; } /^L/{print "_"$0}' sample |
   sort -r
_LAST ...
46m ...
02m22s ...
01m59s ...
01m15s ...
00m28s ...
00m07s ...

其他人仍然表示赞赏。

语言无关 Kubernetes 进行排序

评论

0赞 Paul Hodges 12/14/2022
我的强迫症希望在最近的每条线上都留几分钟,格式化为一致的宽度,如果它们有意义,也要秒(它们不会超过 5m,因为它们会四舍五入)。不过,这个问题的重点是学习算法;我可以通过多种方式让它我想做的事,但好的排序和/或格式化方法总是很方便。你们几乎总是向我展示比我前几次尝试更好的东西。

答:

2赞 Daweo 12/14/2022 #1

我会利用 GNU 来完成这个任务,让内容成为AWKfile.txt

2m36s ...
2m9s ...
28s ...
2m22s ...
2m6s ...
46m ...
7s ...
45m ...
3m9s ...
31m ...
16m ...
75s ...
74s ...
67s ...
46m ...
63s ...
2m15s ...
119s ...
16m ...
75s ...
74s ...
69s ...
46m ...
31m ...
16m ...
75s ...
62s ...

然后

awk '$1!~/m/{$1="0m" $1}{split($1,arr,/m/);$1=arr[1]*60+arr[2];print}' file.txt

给出输出

156 ...
129 ...
28 ...
142 ...
126 ...
2760 ...
7 ...
2700 ...
189 ...
1860 ...
960 ...
75 ...
74 ...
67 ...
2760 ...
63 ...
135 ...
119 ...
960 ...
75 ...
74 ...
69 ...
2760 ...
1860 ...
960 ...
75 ...
62 ...

解释:如果第一个字段中没有我前面的字段,那么我在字符处使用拆分函数,然后我计算值:我乘以 60 之前的内容转换为秒,然后将后面的内容相加以获得以秒为单位的总和,对于没有秒部分的行,秒部分是空字符串,用于算术时变为零。然后,可以对此输出进行数字排序,即m0mmm

awk '$1!~/m/{$1="0m" $1}{split($1,arr,/m/);$1=arr[1]*60+arr[2];print}' file.txt | sort -n

给出输出

7 ...
28 ...
62 ...
63 ...
67 ...
69 ...
74 ...
74 ...
75 ...
75 ...
75 ...
119 ...
126 ...
129 ...
135 ...
142 ...
156 ...
189 ...
960 ...
960 ...
960 ...
1860 ...
1860 ...
2700 ...
2760 ...
2760 ...
2760 ...

(在 GNU Awk 5.0.1 和 sort (GNU coreutils) 8.30 中测试)

评论

0赞 Paul Hodges 12/14/2022
不错。可以得到前导零以实现格式的一致性。将所有内容转换为秒确实可以让我们获得正确的顺序,尽管在末尾附加 s 可能不准确,因为延迟 >5m 将它们四舍五入......(我想?我宁愿仍然看到一种格式,但这只是我生气,如果需要,使用模/余数数学很容易做到。会尝试一个,看看它看起来比我的怪物更好还是更差,哈哈 - 谢谢,值得点赞。printf##m##ssed
0赞 Paul Hodges 12/14/2022
仅供参考 - 输入有一个标头,该标头报告为零,示例文件中的空行也是如此。
0赞 Daweo 12/14/2022
@PaulHodges,只要指定宽度大于 log10(maximal_value_in_seconds) 的条件成立,就可以使用替换sprintf$1=arr[1]*60+arr[2]$1=sprintf("%04d",arr[1]*60+arr[2])
2赞 Andre Wildberg 12/14/2022 #2

如果示例中显示了所有可能的时间格式,则可能会起作用。它显示了文件、输出和最终排序,为清楚起见,粘贴在一起。

它查找 ,乘以 60 并添加任何现有的秒数。 如果未找到,则仅打印秒数。mm

$ paste sample <(awk '/m/{split($1,ar,"m"); print ar[1] * 60 + ar[2]} 
                 !/m/{print $1 * 1}' sample) | sort -nk 3
7s ...  7
28s ... 28
62s ... 62
63s ... 63
67s ... 67
69s ... 69
74s ... 74
74s ... 74
75s ... 75
75s ... 75
75s ... 75
119s ...    119
2m6s ...    126
2m9s ...    129
2m15s ...   135
2m22s ...   142
2m36s ...   156
3m9s ...    189
16m ... 960
16m ... 960
16m ... 960
31m ... 1860
31m ... 1860
45m ... 2700
46m ... 2760
46m ... 2760
46m ... 2760

评论

1赞 Paul Hodges 12/14/2022
甜蜜 - 非常简洁。仍然没有格式化输出,尽管这只是缓解了我的强迫症倾向......
0赞 Ed Morton 12/16/2022
@PaulHodges格式化输出不是您问题的重点吗?你在里面说.您不是还需要将标题行更改为并包含在输出中吗?如果你不需要格式化,而LAST映射,你想要的只是一种对时间戳输出进行排序的方法,那将是一个更容易解决的问题。I'm really only looking for elegant formatting optionsLAST_LAST
0赞 Paul Hodges 12/21/2022
是的,但我仍然喜欢看到各种处理我可以创作的作品的方法......我最感激任何努力......
3赞 Arkadiusz Drabczyk 12/14/2022 #3

仅使用 GNU awk:

awk '{match($1,  /[0-9]+m/, m); match($1, /[0-9]+s/, s)
    arr[m[0]*60 + s[0]] = $0
}
    END {
    n = asorti(arr, sorted, "@ind_num_asc")
    for(i = 1; i <= n; i++)
          print arr[sorted[i]]
}
' sample

看起来比一堆链子干净一点。它打印:

LAST ...
7s ...
28s ...
62s ...
63s ...
67s ...
69s ...
74s ...
75s ...
119s ...
2m6s ...
2m9s ...
2m15s ...
2m22s ...
2m36s ...
3m9s ...
16m ...
31m ...
45m ...
46m ...

因为我实际上认为您希望显示最新的条目 靠近顶部,如果不是,只需将ind_num_asc更改为ind_num_desc

评论

0赞 Eric Marceau 12/15/2022
我同意 不错的工作/逻辑!每天都在学习。
2赞 HatLess 12/14/2022 #4

sed

$ cat script.sed
1! {
        s/^[0-9]s/0m0&/
        /^[0-9]{2,}s/ {
                s/^6/01m0/
                s/^7/01m1/
                s/^8/01m2/
                s/^9/01m3/
                s/^10/01m4/
                s/^11/01m5/
                s/^[0-5][0-9]s/0m&/
        }
        s/^([0-9]m)([0-9]s)/0\10\2/
        s/^([0-9]+m)([0-9]s)/\10\2/
        s/^[0-9]m/0&/
}
1s/^/_/

运行

$ sed -Ef script.sed input_file | sort -r
_LAST ...
46m ...
46m ...
46m ...
45m ...
31m ...
31m ...
16m ...
16m ...
16m ...
03m09s ...
02m36s ...
02m22s ...
02m15s ...
02m09s ...
02m06s ...
01m59s ...
01m15s ...
01m15s ...
01m15s ...
01m14s ...
01m14s ...
01m09s ...
01m07s ...
01m03s ...
01m02s ...
00m28s ...
00m07s ...
1赞 Eric Marceau 12/14/2022 #5

如果你正在寻找一个通用的实用程序来“规范化”报告的值,这里有一个“hack”演示了这种能力。

#!/bin/bash

DBG=1

INPUT=`basename "$0" ".sh" `.input

cat >"${INPUT}" <<"EnDoFiNpUt"
2m36s ...
2m9s ...
28s ...
2m22s ...
2m6s ...
46m ...
7s ...
45m ...
3m9s ...
31m ...
16m ...
75s ...
74s ...
67s ...
46m ...
63s ...
2m15s ...
119s ...
16m ...
75s ...
74s ...
69s ...
46m ...
31m ...
16m ...
75s ...
62s ...
EnDoFiNpUt

#cat >"${INPUT}" <<"EnDoFiNpUt"
#119s ...
#EnDoFiNpUt

awk -v dbg="${DBG}" 'BEGIN{
    split("", times) ;
    items=0 ;
}{
    if( $0 == "" ){
        exit ;
    }else{
        if( dbg == 1 ){ print "\n"$0 | "cat >&2" ; } ;
        rem=$1 ;
        items++ ;

        posH=index( rem, "h" ) ;
        if( posH == 0 ){
            hr=0 ;
            if( dbg == 1 ){ print "\thr = "hr | "cat >&2" ; } ;

            posM=index( rem, "m" ) ;
            if( posM == 0 ){
                min=0 ;
                if( dbg == 1 ){ print "\tmin = "min | "cat >&2" ; } ;

                posS=index( rem, "s" ) ;
                if( posS == 0 ){
                    if( rem = "" ){
                        sec=0 ;
                    }else{
                        minX=sprintf("%d", rem/60 ) ;
                        sec=rem-minX*60 ;
                        min=min+minX ;
                        if( dbg == 1 && minX > 0 ){ print "\t\tmin = "min | "cat >&2" ; } ;
                    } ;
                }else{
                    beg=substr( rem, 1, posS-1) ;
                    if( rem = "" ){
                        sec=0 ;
                    }else{
                        minX=sprintf("%d", beg/60 ) ;
                        sec=beg-minX*60 ;
                        min=min+minX ;
                        if( dbg == 1 && minX > 0 ){ print "\t\tmin = "min | "cat >&2" ; } ;
                    } ;
                } ;
            }else{
                min=substr( rem, 1, posM-1) ;
                rem=substr( rem, posM+1 ) ;
                if( dbg == 1 ){ print "\tmin = "min | "cat >&2" ; } ;

                posS=index( rem, "s" ) ;
                if( posS == 0 ){
                    if( rem = "" ){
                        sec=0 ;
                    }else{
                        minX=sprintf("%d", rem/60 ) ;
                        sec=rem-minX*60 ;
                        min=min+minX ;
                        if( dbg == 1 && minX > 0 ){ print "\t\tmin = "min | "cat >&2" ; } ;
                    } ;
                }else{
                    beg=substr( rem, 1, posS-1) ;
                    if( rem = "" ){
                        sec=0 ;
                    }else{
                        minX=sprintf("%d", beg/60 ) ;
                        sec=beg-minX*60 ;
                        min=min+minX ;
                        if( dbg == 1 && minX > 0 ){ print "\t\tmin = "min | "cat >&2" ; } ;
                    } ;
                } ;
            } ;
        }else{
            hr=substr( rem, 1, posH-1) ;
            rem=substr( rem, posH+1 ) ;
            if( dbg == 1 ){ print "\thr = "hr | "cat >&2" ; } ;

            posM=index( rem, "m" ) ;
            if( posM == 0 ){
                min=0 ;
                if( dbg == 1 ){ print "\tmin = "min | "cat >&2" ; } ;

                posS=index( rem, "s" ) ;
                if( posS == 0 ){
                    if( rem = "" ){
                        sec=0 ;
                    }else{
                        minX=sprintf("%d", rem/60 ) ;
                        sec=rem-minX*60 ;
                        min=min+minX ;
                        if( dbg == 1 && minX > 0 ){ print "\t\tmin = "min | "cat >&2" ; } ;
                    } ;
                }else{
                    beg=substr( rem, 1, posS-1) ;
                    if( rem = "" ){
                        sec=0 ;
                    }else{
                        minX=sprintf("%d", beg/60 ) ;
                        sec=beg-minX*60 ;
                        min=min+minX ;
                        if( dbg == 1 && minX > 0 ){ print "\t\tmin = "min | "cat >&2" ; } ;
                    } ;
                } ;
            }else{
                min=substr( rem, 1, posM-1) ;
                rem=substr( rem, posM+1 ) ;
                if( dbg == 1 ){ print "\tmin = "min | "cat >&2" ; } ;

                posS=index( rem, "s" ) ;
                if( posS == 0 ){
                    if( rem = "" ){
                        sec=0 ;
                    }else{
                        minX=sprintf("%d", rem/60 ) ;
                        sec=rem-minX*60 ;
                        min=min+minX ;
                        if( dbg == 1 && minX > 0 ){ print "\t\tmin = "min | "cat >&2" ; } ;
                    } ;
                }else{
                    beg=substr( rem, 1, posS-1) ;
                    if( rem = "" ){
                        sec=0 ;
                    }else{
                        minX=sprintf("%d", beg/60 ) ;
                        sec=beg-minX*60 ;
                        min=min+minX ;
                        if( dbg == 1 && minX > 0 ){ print "\t\tmin = "min | "cat >&2" ; } ;
                    } ;
                } ;
            } ;
            if( dbg == 1 ){ print "\tsec = "sec | "cat >&2" ; } ;
        } ;
        times[items]=sprintf("%02dh%02dm%02ds", hr, min, sec ) ;
        if( dbg == 1 ){ print "\t"times[items] | "cat >&2" ; } ;
    } ;
}END{
    if( dbg == 1 ){ print "Normalized Values:" } ; 
    for( i=1 ; i <= items ; i++ ){
        print times[i] ;
    } ;
}' "${INPUT}" > "${INPUT}.out"

echo ""
cat "${INPUT}.out"

echo ""
echo "Sorted Values:"
grep -v 'Normalized' "${INPUT}.out" | sort -n

评论

0赞 Paul Hodges 12/14/2022
将标头输出为 。不打印记录的其余部分,而且有点大......不过,如果你把它存放在某个地方,那也没关系。我喜欢内置的调试。00h00m00s
4赞 Ed Morton 12/14/2022 #6

我会先将所有内容转换为秒,然后将其打印为 ,例如:HH:MM:SS

$ cat tst.awk
BEGIN {
    split("h m s",denoms)
    fmts["s"] = fmts["m"] = fmts["h"] = "%02d"
    mults["s"] = 1
    mults["m"] = 60
    mults["h"] = 60 * 60
}
sub(/^L/,"_L") {
    print
    next
}
{
    time = $1

    secs = 0
    while ( match(time,/[0-9]+./) ) {
        value = substr(time,1,RLENGTH-1)
        denom = substr(time,RLENGTH)
        time  = substr(time,RLENGTH+1)
        secs += value * mults[denom]
    }

    for ( i=1; i in denoms; i++ ) {
        denom = denoms[i]
        out = (i>1 ? out ":" : "") sprintf(fmts[denom],int(secs/mults[denom]))
        secs %= mults[denom]
    }

    $1 = out

    print | "sort -r"
}

$ awk -f tst.awk sample
_LAST ...
00:46:00 ...
00:02:22 ...
00:01:59 ...
00:01:15 ...
00:00:28 ...
00:00:07 ...

显然,如果您想包括天数,请在该部分添加定义,并类似地添加其他更长的持续时间。"d"BEGIN

1赞 Paul Hodges 12/15/2022 #7

我要感谢大家的时间和贡献。
不出所料,我学到了一些东西。:)

一位同事看到了这一点,并私下向我发送了一个与我在问题中引用的同一页面中与此解决方案非常相似的解决方案,这有助于我了解我的问题和解决问题的内置方法。我在这里转发了一些解释,希望有人会发现它有用,也许可以帮助我完善自己的理解。

我对典型输出的主要问题是时间字段的格式(imo)非常不一致 - 我假设为了简洁起见,这可能很好 - 并且排序显然是按对象而不是时间(这在许多情况下也有意义)然后对对象的..lastTimestamp

作为记录,列表(以及其他许多)kubectl get --help

--sort-by='': If non-empty, sort list types using this field specification.

从 K8S 对象定义中注意到。以及The field specification is expressed as a JSONPath expression

-o, --output='': Output format. 

它有一长串选项,包括让你“滚动自己的”。-o custom-columns=...

因此,我重建了正常输出,将有问题的第一列替换为实际的一致时间戳字段,并更改了默认排序顺序。

kubectl get events -o custom-columns="TIMESTAMP:{.lastTimestamp},REASON:{.reason},TYPE:{.type},OBJ_NAME:{.involvedObject.name},MESSAGE:{.message}" --sort-by={.lastTimestamp,.type,.reason} 

更好的是,帮助文本直接引用了相关文档,因此我很快就能够将其转换为使用模板文件:

$: cat $HOME/.kube/custCol.txt
TIMESTAMP      REASON  TYPE  OBJ_NAME             MESSAGE
lastTimestamp  reason  type  involvedObject.name  message

$: kubectl get events -o custom-columns-file=$HOME/.kube/custCol.txt --sort-by={.metadata.creationTimestamp,.type,.reason}

仍然很长,但不需要通过管道连接到另一个我可以摸索逻辑的进程。为了简明扼要,更易于打字和阅读,我做了一个别名——

alias events='kubectl get events -o custom-columns-file=$HOME/.kube/custCol.txt --sort-by={.metadata.creationTimestamp,.type,.reason}

现在,我可以通过在末尾添加集群和命名空间来指定它们 -

$: events --context bulk -n bulk-sit1 | head -6
TIMESTAMP             REASON                 TYPE      OBJ_NAME                                     MESSAGE
2022-12-14T16:15:18Z   BackOff                Warning   seasonalsuspends-cron-job-1671034500-7c7md   Back-off restarting failed container
2022-12-14T16:00:29Z   BackOff                Warning   nonpays-cron-job-1671033600-7xhl7            Back-off restarting failed container
2022-12-14T16:06:51Z   BackoffLimitExceeded   Warning   nonpays-cron-job-1671033600                  Job has reached the specified backoff limit
2022-12-14T16:22:07Z   BackoffLimitExceeded   Warning   seasonalsuspends-cron-job-1671034500         Job has reached the specified backoff limit
2022-12-14T15:45:20Z   Completed              Normal    nonpays-cron-job-1671032700                  Job completed

还没有弄清楚内置过滤器,所以现在我有一个功能,只用于警告 -grep

warnings() { local args=(get events --sort-by={.lastTimestamp,.type,.reason} "$@" 
   -o custom-columns=TIMESTAMP:{.lastTimestamp},REASON:{.reason},TYPE:{.type},OBJ_NAME:{.involvedObject.name},MESSAGE:{.message});
  kubectl "${args[@]}" | grep ' Warning '
}

希望有人能从中得到一些用处。