分类 默认分类 下的文章

jcmd 的 GC.class_stats 子命令

jcmd 的 GC.class_stats 子命令 输出 JVM 里metaspace (元数据)区的 class 相关的信息. 这里必须记住, 所有的 java.lang.Class 及其子类都在heap, 元数据区只是这些class的元数据, 元数据区存的也不是这些类编译后的字节码(bytescode).

GC.class_stats 能输出每个class 在 metaspace 空间占用的大小.

这个子命令在JDK 14 deprecated, 15去掉.

默认只输出下面这些列, 还有更多列可以显示, 需要指定列名称:

InstBytes,KlassBytes,CpAll,annotations,MethodCount,Bytecodes,MethodAll,ROAll,RWAll,Total
$ bin/jcmd <pid> GC.class_stats

由于类太多, 并且长度会折叠, 这里截图开头和结尾的截图:
head.png
tail.png

最好结合 jcmd <pid> VM.metaspace 一起看
vmMetaspace.png

- 阅读剩余部分 -

JVM 的 TimeZone 被别人改了

昨天有同事让我看看为啥一台QA的机器上的时区变了, 同样的代码在生产环境显示的日期都是 MST 时区(如:Tue Dec 06 06:52:22 MST 2022), 可是QA 环境显示的时间都是 UTC 时区(如: (如:Tue Dec 06 013:52:22 UTC 2022)).

检查步骤:

首先确认这个 Linux 机器上的时区

$ date
Tue Dec 06 013:52:22 UTC 2022

通过以上代码确认, 这个Linux 机器时区是正确的

检查 JVM 的时区

  1. JVM 可以通过启动参数添加: -Duser.timezone="XXXX/YYYY"

    java -Duser.timezone="Asia/Kolkata" com.tianxiaohui.AppMain
  2. 又或者设置系统环境变量添加

    System.setProperty("user.timezone", "Asia/Kolkata");
  3. 又或者通过设置默认时区:

    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

对于第一种方式, 可以通过检查Java 进程的启动命令获得(这里假设pid是44848), 发现命令行没设置时区.

cat /proc/44848/cmdline
````
对于第二种方式, 我们首先通过查 看 ```cat /proc/44848/environ``` 的方式去查看, 没发现这个环境变量. 不过我们通过jdk 自带的命令的方式却发现了:

$ bin/jcmd 44848 VM.system_properties
java.version=1.8.0_342
user.timezone=America/Phoenix
sun.arch.data.model=64

虽然找到了系统环境变量, 却发现这里是正确的, 并不是Date.toString() 表现出的UTC 时区. 于是就只能检查是不是第三种设置的. 

# 如何查看当前运行中的JVM里面的默认时区
1. 可以通过 JVM attach agent的方式去查看, 要自己写个 Agent, 可以参考这个简单的 Agent: https://github.com/manecocomph/myJavaAgent/blob/962f424176e02b9638fec87a0a5d1bad9cfaf0b2/src/com/tianxiaohui/java/agent/SampleAgent.java
当然, 你可以通过 Btrace 不安全的方式,找个容易控制的拦截点, 然后打印 默认时区. 

2. 另外一种方式就是直接做一个heap, 直接查看heap 里面的 Timezone 找个class的字段, 我们就采取了这种方式, 打开 heap dump, 找到这个 java.util.TimeZone 类, 然后查看其静态字段defaultTimezone, 直接可以看到被设置的时区. 
第一步找到这个类:
![heap1.png][1]
查看其 静态字段 defaultTimeZone
![defaultTZ.png][2]

# 找到代码
既然确认是通过代码设置默认时区, 那么直接搜索代码就找到了. 原来他们在最近的代码改动中, 有人为了某个feature, 直接修改了系统 TimeZone, 但是其本来只是想看看另外一个时区的时间. 


[1]: https://www.tianxiaohui.com/usr/uploads/2022/12/2240322561.png

mongo express MongoError: command listCollections requires authentication

为了连接一个MongoDB server 省事, 不想装本地app, 于是想使用docker 装一个 Web 版本的 Mongo express. 在启动的时候, 总是报这个错: MongoError: command listCollections requires authentication

我的连接URL是: mongodb://user1:pwd1@mymongo.tianxioahui.com:27017/test_db. 可是根据官方的说明, 不论怎么写 docker command 都不行.

$docker run --rm -e ME_CONFIG_MONGODB_SERVER=mymongo.tianxioahui.com \
                 -e ME_CONFIG_BASICAUTH_USERNAME=user1 \
                 -e ME_CONFIG_BASICAUTH_PASSWORD=pwd1 \
                 -e ME_CONFIG_MONGODB_ENABLE_ADMIN=false \
                 -e ME_CONFIG_MONGODB_AUTH_DATABASE=test_db
                 -p 8083:8081 --name myMongo  mongo-express

(node:6) UnhandledPromiseRejectionWarning: MongoError: command listCollections requires authentication
    at Connection.<anonymous> (/node_modules/mongodb/lib/core/connection/pool.js:453:61)
    at Connection.emit (events.js:314:20)
    at processMessage (/node_modules/mongodb/lib/core/connection/connection.js:456:10)
    at Socket.<anonymous> (/node_modules/mongodb/lib/core/connection/connection.js:625:15)
    at Socket.emit (events.js:314:20)
    at addChunk (_stream_readable.js:297:12)
    at readableAddChunk (_stream_readable.js:272:9)
    at Socket.Readable.push (_stream_readable.js:213:10)
    at TCP.onStreamRead (internal/stream_base_commons.js:188:23)

可是不论怎么调可用的参数, 总是报这个错. Google 了一下, 发现2021年6月就有人报这个错: https://github.com/mongo-express/mongo-express/issues/720

解决方式也很简单, 直接用一个连接URL 替换其他环境变量:

sudo docker run --rm -e ME_CONFIG_MONGODB_URL=mongodb://user1:pwd1@mymongo.tianxioahui.com:27017/test_db  -p 8083:8081 --name myMongo mongo-express

可是, 可是, 这个环境变量ME_CONFIG_MONGODB_URLhttps://hub.docker.com/_/mongo-express 竟然没有, 可是能用, 还很管用.

xxx Is Damaged and Can’t Be Opened. You Should Move It To The Trash

最近拿到公司 ARM 芯片的 Mac Pro, 一番设置, 可是新新下载的软件, 比如JDK, 总是报下面的错, 无法运行:
“xxx Is Damaged and Can’t Be Opened. You Should Move It To The Trash“
damage.png

如何修复

google 到这个修复方法: https://discussions.apple.com/thread/253714860

$ xattr -c <path/to/application.app>

使用上面的方法对 java 做上述操作, 还是一样的错误, 一度怀疑这个不行. 但是通过 xattr 查询它的属性, 发现又是相关. 最终发现这么解决: 对目录里面每层文件都做这个操作:

eric@Q67J490MY0 bin % pwd
/Users/eric/work/tools/jdks/jdk17.0.3.1/bin
eric@Q67J490MY0 bin % xattr -c *
eric@Q67J490MY0 bin % cd ..
eric@Q67J490MY0 jdk17.0.3.1 % xattr -c *
eric@Q67J490MY0 jdk17.0.3.1 % ./bin/java

上面的操作是对每个文件都去掉xattr的那些属性.

更多

xattr -h #查看帮助

python 使用 cProfile 做 profiling

最近开始看机器学习的项目, 于是开始看 Python 的代码. 把一个机器学习的模型发布上 prod 去预测结果, 发现生产环境里面 的性能很差: 本地 1s 能跑完的 API, 在生产环境需要 30 多毫秒. 先是看了下基本情况, 发现生产环境在预测那段代码, 竟然起了 50 多个 Python 线程. 于是怀疑生产环境因为使用 container, 但是却拿到了宿主机的 CPU 数量, 于是开了很多线程. 但是 container 却限制了 cpu 的使用量, 导致多线程竞争, 最终性能下降.

于是尝试做 profiling: cProfile 是python 自带的.

要做 profiling 的部分:

import os
import time
import cProfile
from transformers import BertTokenizer, BertModel

pretrained_model_path = os.path.abspath(os.path.dirname(__file__)) + '/bert-base-uncased'
bert_tokenizer = BertTokenizer.from_pretrained(pretrained_model_path, cache_dir='/tmp')
bert_model = BertModel.from_pretrained(pretrained_model_path)

s = "This brings us to the downsides"

def bert_function():
    t0 = time.time()
    for i in range(0, 10):
        inputs = bert_tokenizer(s, return_tensors="pt")
        outputs = bert_model(**inputs)

    print("used: {}".format((time.time() - t0)))

cProfile.run('bert_function()', 'my.prof')

执行

python test.py

使用 flameprof 转成 火焰图

python -m flameprof my.prof > my.svg

结果:
out_svg.png

参考:
https://docs.python.org/3/library/profile.html