Linux కెర్నల్ మరియు అప్లికేషన్లను డీబగ్గింగ్ చేయడానికి పెద్ద సంఖ్యలో సాధనాలను కలిగి ఉంది. వాటిలో ఎక్కువ భాగం అప్లికేషన్ పనితీరుపై ప్రతికూల ప్రభావాన్ని చూపుతాయి మరియు ఉత్పత్తిలో ఉపయోగించబడవు.
కొన్ని సంవత్సరాల క్రితం ఉంది
eBPFని ఉపయోగించే అనేక అప్లికేషన్ యుటిలిటీలు ఇప్పటికే ఉన్నాయి మరియు ఈ కథనంలో లైబ్రరీ ఆధారంగా మీ స్వంత ప్రొఫైలింగ్ యుటిలిటీని ఎలా వ్రాయాలో చూద్దాం.
Ceph నెమ్మదిగా ఉంది
Ceph క్లస్టర్కి కొత్త హోస్ట్ జోడించబడింది. దానికి కొంత డేటాను మైగ్రేట్ చేసిన తర్వాత, దాని ద్వారా వ్రాత అభ్యర్థనలను ప్రాసెస్ చేసే వేగం ఇతర సర్వర్ల కంటే చాలా తక్కువగా ఉందని మేము గమనించాము.
ఇతర ప్లాట్ఫారమ్ల వలె కాకుండా, ఈ హోస్ట్ bcache మరియు కొత్త linux 4.15 కెర్నల్ను ఉపయోగించింది. ఈ కాన్ఫిగరేషన్ యొక్క హోస్ట్ను ఇక్కడ ఉపయోగించడం ఇదే మొదటిసారి. మరియు ఆ సమయంలో సమస్య యొక్క మూలం సిద్ధాంతపరంగా ఏదైనా కావచ్చునని స్పష్టమైంది.
హోస్ట్ను విచారిస్తోంది
ceph-osd ప్రక్రియలో ఏమి జరుగుతుందో చూడటం ద్వారా ప్రారంభిద్దాం. దీని కోసం మేము ఉపయోగిస్తాము
చిత్రం ఫంక్షన్ అని మాకు చెబుతుంది fdatasync() ఫంక్షన్లకు రిక్వెస్ట్ పంపడానికి చాలా సమయం వెచ్చించారు generic_make_request(). దీనర్థం, సమస్యలకు కారణం osd డెమోన్ వెలుపల ఎక్కడో ఉంటుంది. ఇది కెర్నల్ లేదా డిస్క్లు కావచ్చు. iostat అవుట్పుట్ bcache డిస్క్ల ద్వారా అభ్యర్థనలను ప్రాసెస్ చేయడంలో అధిక జాప్యాన్ని చూపింది.
హోస్ట్ని తనిఖీ చేస్తున్నప్పుడు, systemd-udevd డెమోన్ పెద్ద మొత్తంలో CPU సమయాన్ని వినియోగిస్తున్నట్లు మేము కనుగొన్నాము - అనేక కోర్లలో దాదాపు 20%. ఇది వింత ప్రవర్తన, కాబట్టి మీరు ఎందుకు కనుగొనాలి. Systemd-udevd ueventsతో పని చేస్తుంది కాబట్టి, మేము వాటిని చూడాలని నిర్ణయించుకున్నాము udevadm మానిటర్. సిస్టమ్లోని ప్రతి బ్లాక్ పరికరానికి పెద్ద సంఖ్యలో మార్పు ఈవెంట్లు సృష్టించబడినట్లు ఇది మారుతుంది. ఇది చాలా అసాధారణమైనది, కాబట్టి ఈ ఈవెంట్లన్నింటిని ఏది ఉత్పత్తి చేస్తుందో మనం చూడాలి.
BCC టూల్కిట్ని ఉపయోగించడం
మేము ఇప్పటికే కనుగొన్నట్లుగా, కెర్నల్ (మరియు సిస్టమ్ కాల్లోని సెఫ్ డెమోన్) చాలా సమయాన్ని వెచ్చిస్తుంది generic_make_request(). ఈ ఫంక్షన్ యొక్క వేగాన్ని కొలవడానికి ప్రయత్నిద్దాం. IN
ఈ ఫీచర్ సాధారణంగా త్వరగా పని చేస్తుంది. డివైజ్ డ్రైవర్ క్యూకు అభ్యర్థనను పంపడమే ఇది చేస్తుంది.
Bcache వాస్తవానికి మూడు డిస్క్లను కలిగి ఉండే సంక్లిష్ట పరికరం:
- బ్యాకింగ్ పరికరం (కాష్డ్ డిస్క్), ఈ సందర్భంలో ఇది నెమ్మదిగా HDD;
- కాషింగ్ పరికరం (కాషింగ్ డిస్క్), ఇక్కడ ఇది NVMe పరికరం యొక్క ఒక విభజన;
- అప్లికేషన్ రన్ అయ్యే bcache వర్చువల్ పరికరం.
అభ్యర్థన ప్రసారం నెమ్మదిగా జరుగుతుందని మాకు తెలుసు, అయితే ఈ పరికరాల్లో దేనికి? మేము దీనితో కొంచెం తర్వాత వ్యవహరిస్తాము.
ఈవెంట్లు సమస్యలను కలిగించే అవకాశం ఉందని ఇప్పుడు మనకు తెలుసు. వారి తరానికి సరిగ్గా కారణమేమిటో కనుగొనడం అంత సులభం కాదు. ఇది క్రమానుగతంగా ప్రారంభించబడే ఒక రకమైన సాఫ్ట్వేర్ అని అనుకుందాం. స్క్రిప్ట్ని ఉపయోగించి సిస్టమ్లో ఎలాంటి సాఫ్ట్వేర్ రన్ అవుతుందో చూద్దాం execsnoop అదే నుండి
ఉదాహరణకు ఇలా:
/usr/share/bcc/tools/execsnoop | tee ./execdump
మేము ఇక్కడ execsnoop యొక్క పూర్తి అవుట్పుట్ను చూపము, కానీ మాకు ఆసక్తి ఉన్న ఒక లైన్ ఇలా ఉంది:
sh 1764905 5802 0 sudo arcconf getconfig 1 AD | grep Temperature | awk -F '[:/]' '{print $2}' | sed 's/^ ([0-9]*) C.*/1/'
మూడవ నిలువు వరుస ప్రక్రియ యొక్క PPID (పేరెంట్ PID). PID 5802తో ప్రక్రియ మా పర్యవేక్షణ వ్యవస్థ యొక్క థ్రెడ్లలో ఒకటిగా మారింది. పర్యవేక్షణ వ్యవస్థ యొక్క కాన్ఫిగరేషన్ను తనిఖీ చేస్తున్నప్పుడు, తప్పు పారామితులు కనుగొనబడ్డాయి. HBA అడాప్టర్ యొక్క ఉష్ణోగ్రత ప్రతి 30 సెకన్లకు తీసుకోబడుతుంది, ఇది అవసరమైన దానికంటే చాలా తరచుగా ఉంటుంది. చెక్ విరామాన్ని పెద్దదానికి మార్చిన తర్వాత, ఇతర హోస్ట్లతో పోల్చితే ఈ హోస్ట్లోని అభ్యర్థన ప్రాసెసింగ్ జాప్యం ఇకపై ప్రత్యేకంగా లేదని మేము కనుగొన్నాము.
కానీ bcache పరికరం ఎందుకు నెమ్మదిగా ఉందో ఇప్పటికీ అస్పష్టంగా ఉంది. మేము ఒకే విధమైన కాన్ఫిగరేషన్తో టెస్ట్ ప్లాట్ఫారమ్ను సిద్ధం చేసాము మరియు bcacheలో fioని అమలు చేయడం ద్వారా సమస్యను పునరుత్పత్తి చేయడానికి ప్రయత్నించాము, uevents రూపొందించడానికి udevadm ట్రిగ్గర్ను క్రమానుగతంగా అమలు చేస్తాము.
BCC-ఆధారిత సాధనాలను వ్రాయడం
నెమ్మదిగా కాల్లను కనుగొనడానికి మరియు ప్రదర్శించడానికి సరళమైన యుటిలిటీని వ్రాయడానికి ప్రయత్నిద్దాం generic_make_request(). ఈ ఫంక్షన్ పిలిచిన డ్రైవ్ పేరుపై కూడా మాకు ఆసక్తి ఉంది.
ప్రణాళిక సులభం:
- నమోదు చేసుకోండి kprobe న generic_make_request():
- మేము డిస్క్ పేరును మెమరీలో సేవ్ చేస్తాము, ఫంక్షన్ ఆర్గ్యుమెంట్ ద్వారా యాక్సెస్ చేయవచ్చు;
- మేము టైమ్స్టాంప్ను సేవ్ చేస్తాము.
- నమోదు చేసుకోండి kretprobe నుండి తిరిగి రావడానికి generic_make_request():
- మేము ప్రస్తుత సమయముద్రను పొందుతాము;
- మేము సేవ్ చేసిన టైమ్స్టాంప్ కోసం చూస్తాము మరియు దానిని ప్రస్తుత దానితో పోల్చాము;
- ఫలితం పేర్కొన్నదాని కంటే ఎక్కువగా ఉంటే, మేము సేవ్ చేసిన డిస్క్ పేరును కనుగొని టెర్మినల్లో ప్రదర్శిస్తాము.
Kprobes и kretprobes ఫ్లైలో ఫంక్షన్ కోడ్ని మార్చడానికి బ్రేక్పాయింట్ మెకానిజంను ఉపయోగించండి. మీరు చదవగలరు
పైథాన్ స్క్రిప్ట్లోని eBPF టెక్స్ట్ ఇలా కనిపిస్తుంది:
bpf_text = “”” # Here will be the bpf program code “””
ఫంక్షన్ల మధ్య డేటాను మార్పిడి చేయడానికి, eBPF ప్రోగ్రామ్లు ఉపయోగిస్తాయి
struct data_t {
u64 pid;
u64 ts;
char comm[TASK_COMM_LEN];
u64 lat;
char disk[DISK_NAME_LEN];
};
BPF_HASH(p, u64, struct data_t);
BPF_PERF_OUTPUT(events);
ఇక్కడ మేము అనే హాష్ పట్టికను నమోదు చేస్తాము p, కీ రకంతో u64 మరియు రకం విలువ struct data_t. మా BPF ప్రోగ్రామ్ సందర్భంలో పట్టిక అందుబాటులో ఉంటుంది. BPF_PERF_OUTPUT మాక్రో అనే మరొక పట్టికను నమోదు చేస్తుంది ఈవెంట్స్, ఇది కోసం ఉపయోగించబడుతుంది
ఫంక్షన్కు కాల్ చేయడం మరియు దాని నుండి తిరిగి రావడం లేదా వివిధ ఫంక్షన్లకు కాల్ల మధ్య ఆలస్యాలను కొలిచేటప్పుడు, అందుకున్న డేటా తప్పనిసరిగా ఒకే సందర్భానికి చెందినదని మీరు పరిగణనలోకి తీసుకోవాలి. మరో మాటలో చెప్పాలంటే, మీరు ఫంక్షన్ల సమాంతర ప్రయోగ గురించి గుర్తుంచుకోవాలి. ఒక ప్రక్రియ సందర్భంలో ఫంక్షన్కు కాల్ చేయడం మరియు మరొక ప్రక్రియ సందర్భంలో ఆ ఫంక్షన్ నుండి తిరిగి రావడం మధ్య జాప్యాన్ని కొలవగల సామర్థ్యం మాకు ఉంది, కానీ ఇది నిరుపయోగంగా ఉంటుంది. ఇక్కడ ఒక మంచి ఉదాహరణ ఉంటుంది
తరువాత, అధ్యయనంలో ఉన్న ఫంక్షన్ని పిలిచినప్పుడు అమలు చేయబడే కోడ్ను మనం వ్రాయాలి:
void start(struct pt_regs *ctx, struct bio *bio) {
u64 pid = bpf_get_current_pid_tgid();
struct data_t data = {};
u64 ts = bpf_ktime_get_ns();
data.pid = pid;
data.ts = ts;
bpf_probe_read_str(&data.disk, sizeof(data.disk), (void*)bio->bi_disk->disk_name);
p.update(&pid, &data);
}
ఇక్కడ కాల్ చేసిన ఫంక్షన్ యొక్క మొదటి ఆర్గ్యుమెంట్ రెండవ ఆర్గ్యుమెంట్గా భర్తీ చేయబడుతుంది
నుండి తిరిగి వచ్చినప్పుడు కింది ఫంక్షన్ కాల్ చేయబడుతుంది generic_make_request():
void stop(struct pt_regs *ctx) {
u64 pid = bpf_get_current_pid_tgid();
u64 ts = bpf_ktime_get_ns();
struct data_t* data = p.lookup(&pid);
if (data != 0 && data->ts > 0) {
bpf_get_current_comm(&data->comm, sizeof(data->comm));
data->lat = (ts - data->ts)/1000;
if (data->lat > MIN_US) {
FACTOR
data->pid >>= 32;
events.perf_submit(ctx, data, sizeof(struct data_t));
}
p.delete(&pid);
}
}
ఈ ఫంక్షన్ మునుపటి మాదిరిగానే ఉంటుంది: మేము ప్రాసెస్ మరియు టైమ్స్టాంప్ యొక్క PIDని కనుగొంటాము, కానీ కొత్త డేటా నిర్మాణం కోసం మెమరీని కేటాయించము. బదులుగా, మేము కీ == ప్రస్తుత PIDని ఉపయోగించి ఇప్పటికే ఉన్న నిర్మాణం కోసం హాష్ పట్టికను శోధిస్తాము. నిర్మాణం కనుగొనబడితే, మేము నడుస్తున్న ప్రక్రియ పేరును కనుగొని దానికి జోడిస్తాము.
థ్రెడ్ GIDని పొందడానికి మనం ఇక్కడ ఉపయోగించే బైనరీ షిఫ్ట్ అవసరం. ఆ. మేము పని చేస్తున్న సందర్భంలో థ్రెడ్ను ప్రారంభించిన ప్రధాన ప్రక్రియ యొక్క PID. మేము పిలిచే ఫంక్షన్
టెర్మినల్కు అవుట్పుట్ చేస్తున్నప్పుడు, మాకు ప్రస్తుతం స్ట్రీమ్పై ఆసక్తి లేదు, కానీ ప్రధాన ప్రక్రియపై మాకు ఆసక్తి ఉంది. ఇచ్చిన థ్రెషోల్డ్తో ఫలిత ఆలస్యాన్ని పోల్చిన తర్వాత, మేము మా నిర్మాణాన్ని పాస్ చేస్తాము సమాచారం పట్టిక ద్వారా వినియోగదారు స్థలంలోకి ఈవెంట్స్, దాని తర్వాత మేము ఎంట్రీని తొలగిస్తాము p.
ఈ కోడ్ని లోడ్ చేసే పైథాన్ స్క్రిప్ట్లో, మేము MIN_US మరియు FACTORని ఆలస్యం థ్రెషోల్డ్లు మరియు టైమ్ యూనిట్లతో భర్తీ చేయాలి, వీటిని మేము ఆర్గ్యుమెంట్ల ద్వారా పంపుతాము:
bpf_text = bpf_text.replace('MIN_US',str(min_usec))
if args.milliseconds:
bpf_text = bpf_text.replace('FACTOR','data->lat /= 1000;')
label = "msec"
else:
bpf_text = bpf_text.replace('FACTOR','')
label = "usec"
ఇప్పుడు మనం BPF ప్రోగ్రామ్ను సిద్ధం చేయాలి
b = BPF(text=bpf_text)
b.attach_kprobe(event="generic_make_request",fn_name="start")
b.attach_kretprobe(event="generic_make_request",fn_name="stop")
మేము కూడా నిర్ణయించవలసి ఉంటుంది struct data_t మా స్క్రిప్ట్లో, లేకుంటే మనం ఏమీ చదవలేము:
TASK_COMM_LEN = 16 # linux/sched.h
DISK_NAME_LEN = 32 # linux/genhd.h
class Data(ct.Structure):
_fields_ = [("pid", ct.c_ulonglong),
("ts", ct.c_ulonglong),
("comm", ct.c_char * TASK_COMM_LEN),
("lat", ct.c_ulonglong),
("disk",ct.c_char * DISK_NAME_LEN)]
టెర్మినల్కు డేటాను అవుట్పుట్ చేయడం చివరి దశ:
def print_event(cpu, data, size):
global start
event = ct.cast(data, ct.POINTER(Data)).contents
if start == 0:
start = event.ts
time_s = (float(event.ts - start)) / 1000000000
print("%-18.9f %-16s %-6d %-1s %s %s" % (time_s, event.comm, event.pid, event.lat, label, event.disk))
b["events"].open_perf_buffer(print_event)
# format output
start = 0
while 1:
try:
b.perf_buffer_poll()
except KeyboardInterrupt:
exit()
స్క్రిప్ట్ కూడా అందుబాటులో ఉంది
చివరగా! ఇప్పుడు మనం స్టాలింగ్ bcache పరికరం లాగా కనిపించేది నిజానికి స్టాలింగ్ కాల్ అని చూస్తాము generic_make_request() కాష్ చేసిన డిస్క్ కోసం.
కెర్నల్లోకి తవ్వండి
అభ్యర్థన ప్రసార సమయంలో సరిగ్గా వేగాన్ని తగ్గించడం ఏమిటి? అభ్యర్థన అకౌంటింగ్ ప్రారంభానికి ముందే ఆలస్యం జరుగుతుందని మేము చూస్తాము, అనగా. దానిపై (/proc/diskstats లేదా iostat) గణాంకాల యొక్క తదుపరి అవుట్పుట్ కోసం నిర్దిష్ట అభ్యర్థనను లెక్కించడం ఇంకా ప్రారంభించబడలేదు. సమస్యను పునరుత్పత్తి చేస్తున్నప్పుడు iostatని అమలు చేయడం ద్వారా దీన్ని సులభంగా ధృవీకరించవచ్చు లేదా
ఫంక్షన్ చూస్తే generic_make_request(), అభ్యర్థన అకౌంటింగ్ ప్రారంభించే ముందు, మరో రెండు విధులు పిలవబడతాయని మేము చూస్తాము. ప్రధమ - generic_make_request_checks(), డిస్క్ సెట్టింగ్లకు సంబంధించి అభ్యర్థన యొక్క చట్టబద్ధతపై తనిఖీలను నిర్వహిస్తుంది. రెండవ -
ret = wait_event_interruptible(q->mq_freeze_wq,
(atomic_read(&q->mq_freeze_depth) == 0 &&
(preempt || !blk_queue_preempt_only(q))) ||
blk_queue_dying(q));
అందులో, క్యూ స్తంభింపజేయడానికి కెర్నల్ వేచి ఉంటుంది. జాప్యాన్ని కొలుద్దాం blk_queue_enter():
~# /usr/share/bcc/tools/funclatency blk_queue_enter -i 1 -m
Tracing 1 functions for "blk_queue_enter"... Hit Ctrl-C to end.
msecs : count distribution
0 -> 1 : 341 |****************************************|
msecs : count distribution
0 -> 1 : 316 |****************************************|
msecs : count distribution
0 -> 1 : 255 |****************************************|
2 -> 3 : 0 | |
4 -> 7 : 0 | |
8 -> 15 : 1 | |
మేము ఒక పరిష్కారానికి దగ్గరగా ఉన్నట్లు కనిపిస్తోంది. క్యూను స్తంభింపజేయడానికి/అన్ఫ్రీజ్ చేయడానికి ఉపయోగించే విధులు
ఈ క్యూను క్లియర్ చేయడానికి పట్టే సమయం డిస్క్ లేటెన్సీకి సమానం, ఎందుకంటే క్యూలో ఉన్న అన్ని ఆపరేషన్లు పూర్తయ్యే వరకు కెర్నల్ వేచి ఉంది. క్యూ ఖాళీ అయిన తర్వాత, సెట్టింగ్ల మార్పులు వర్తింపజేయబడతాయి. దాని తర్వాత అంటారు
ఇప్పుడు పరిస్థితిని సరిదిద్దడానికి మాకు తగినంత తెలుసు. udevadm ట్రిగ్గర్ ఆదేశం బ్లాక్ పరికరం కోసం సెట్టింగ్లను వర్తింపజేస్తుంది. ఈ సెట్టింగ్లు udev నియమాలలో వివరించబడ్డాయి. ఏ సెట్టింగ్లు క్యూను స్తంభింపజేస్తున్నాయో వాటిని sysfs ద్వారా మార్చడానికి ప్రయత్నించడం ద్వారా లేదా కెర్నల్ సోర్స్ కోడ్ని చూడటం ద్వారా మనం కనుగొనవచ్చు. మేము BCC యుటిలిటీని కూడా ప్రయత్నించవచ్చు
~# /usr/share/bcc/tools/trace blk_freeze_queue -K -U
PID TID COMM FUNC
3809642 3809642 systemd-udevd blk_freeze_queue
blk_freeze_queue+0x1 [kernel]
elevator_switch+0x29 [kernel]
elv_iosched_store+0x197 [kernel]
queue_attr_store+0x5c [kernel]
sysfs_kf_write+0x3c [kernel]
kernfs_fop_write+0x125 [kernel]
__vfs_write+0x1b [kernel]
vfs_write+0xb8 [kernel]
sys_write+0x55 [kernel]
do_syscall_64+0x73 [kernel]
entry_SYSCALL_64_after_hwframe+0x3d [kernel]
__write_nocancel+0x7 [libc-2.23.so]
[unknown]
3809631 3809631 systemd-udevd blk_freeze_queue
blk_freeze_queue+0x1 [kernel]
queue_requests_store+0xb6 [kernel]
queue_attr_store+0x5c [kernel]
sysfs_kf_write+0x3c [kernel]
kernfs_fop_write+0x125 [kernel]
__vfs_write+0x1b [kernel]
vfs_write+0xb8 [kernel]
sys_write+0x55 [kernel]
do_syscall_64+0x73 [kernel]
entry_SYSCALL_64_after_hwframe+0x3d [kernel]
__write_nocancel+0x7 [libc-2.23.so]
[unknown]
Udev నియమాలు చాలా అరుదుగా మారుతాయి మరియు సాధారణంగా ఇది నియంత్రిత పద్ధతిలో జరుగుతుంది. కాబట్టి ఇప్పటికే సెట్ చేసిన విలువలను కూడా వర్తింపజేయడం వల్ల అభ్యర్థనను అప్లికేషన్ నుండి డిస్క్కి బదిలీ చేయడంలో ఆలస్యం పెరుగుతుందని మేము చూస్తాము. వాస్తవానికి, డిస్క్ కాన్ఫిగరేషన్లో మార్పులు లేనప్పుడు udev ఈవెంట్లను రూపొందించడం (ఉదాహరణకు, పరికరం మౌంట్ చేయబడలేదు/డిస్కనెక్ట్ చేయబడలేదు) మంచి పద్ధతి కాదు. అయినప్పటికీ, మేము కెర్నల్ అనవసరమైన పనిని చేయకుండా సహాయం చేయవచ్చు మరియు అది అవసరం లేకుంటే అభ్యర్థన క్యూను స్తంభింపజేయవచ్చు.
ముగింపు
eBPF చాలా సౌకర్యవంతమైన మరియు శక్తివంతమైన సాధనం. వ్యాసంలో మేము ఒక ఆచరణాత్మక కేసును చూశాము మరియు ఏమి చేయవచ్చో దానిలో ఒక చిన్న భాగాన్ని ప్రదర్శించాము. BCC యుటిలిటీలను అభివృద్ధి చేయడంలో మీకు ఆసక్తి ఉంటే, దాన్ని పరిశీలించడం విలువైనదే
eBPF ఆధారంగా ఇతర ఆసక్తికరమైన డీబగ్గింగ్ మరియు ప్రొఫైలింగ్ సాధనాలు ఉన్నాయి. వారిలో వొకరు -
మూలం: www.habr.com