1/* 2 * Copyright (C) 2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15import { query } from '../SqlLite'; 16import { FuncStruct } from '../ui-worker/ProcedureWorkerFunc'; 17import { SearchFuncBean } from '../../bean/SearchFuncBean'; 18import { SelectionData } from '../../bean/BoxSelection'; 19import { HeapTraceFunctionInfo } from '../../../js-heap/model/DatabaseStruct'; 20import { FunctionItem } from '../../bean/BinderProcessThread'; 21import { StateGroup } from '../../bean/StateModle'; 22import { FuncNameCycle } from '../../bean/BinderProcessThread'; 23import { Utils } from '../../component/trace/base/Utils'; 24 25export const queryFuncNameCycle = ( 26 funcName: string, 27 tIds: string, 28 leftNS: number, 29 rightNS: number 30): Promise<Array<FunctionItem>> => 31 query( 32 'queryFuncNameCycle', 33 ` 34 SELECT 35 c.ts - r.start_ts AS cycleStartTime, 36 c.dur, 37 c.id, 38 t.tid, 39 p.pid 40 FROM 41 callstack c, trace_range r 42 LEFT JOIN 43 thread t 44 ON 45 c.callid = t.id 46 LEFT JOIN 47 process p 48 ON 49 t.ipid = p.id 50 WHERE 51 c.name like '${funcName}%' 52 AND 53 t.tid = ${tIds} 54 AND NOT 55 ((cycleStartTime < ${leftNS}) 56 OR 57 ((c.ts - r.start_ts + c.dur) > ${rightNS})) 58 `, 59 { 60 $funcName: funcName, 61 $tIds: tIds, 62 $leftNS: leftNS, 63 $rightNS: rightNS, 64 }, 65 { traceId: Utils.currentSelectTrace } 66 ); 67 68export const querySingleFuncNameCycle = ( 69 funcName: string, 70 tIds: string, 71 leftNS: number, 72 rightNS: number 73): Promise<Array<FunctionItem>> => 74 query( 75 'querySingleFuncNameCycle', 76 ` 77 SELECT 78 c.name AS funcName, 79 c.ts - r.start_ts AS cycleStartTime, 80 c.dur AS cycleDur, 81 c.id, 82 t.tid, 83 p.pid, 84 c.ts - r.start_ts + c.dur AS endTime 85 FROM 86 callstack c, trace_range r 87 LEFT JOIN 88 thread t 89 ON 90 c.callid = t.id 91 LEFT JOIN 92 process p 93 ON 94 t.ipid = p.id 95 WHERE 96 c.name = '${funcName}' 97 AND 98 t.tid = ${tIds} 99 AND NOT 100 ((cycleStartTime < ${leftNS}) 101 OR 102 (endTime > ${rightNS})) 103 `, 104 { 105 $funcName: funcName, 106 $tIds: tIds, 107 $leftNS: leftNS, 108 $rightNS: rightNS, 109 } 110 ); 111 112export const queryAllFuncNames = async (traceId?: string): Promise<Array<unknown>> => { 113 let list = await query( 114 'queryIsColorIndex', 115 `select 116 colorIndex 117 from 118 callstack 119 limit 1;`, 120 {}, 121 { traceId: traceId, action: 'exec-buf' } 122 ); 123 let isColorIndex = list.length !== 0 ? true : false; 124 let colorIndexStr = isColorIndex ? ',colorIndex' : ''; 125 let allFuncNamesBuffer = await query( 126 'queryAllFuncNames', 127 `select 128 id, 129 name 130 ${colorIndexStr} 131 from 132 callstack;`, 133 {}, 134 { traceId: traceId, action: 'exec-buf' } 135 ); 136 // @ts-ignore 137 return Utils.convertJSON(allFuncNamesBuffer); 138}; 139 140export const queryProcessAsyncFunc = ( 141 traceRange: { 142 startTs: number; 143 endTs: number; 144 }, 145 traceId?: string 146): //@ts-ignore 147 Promise<Array<unknown>> => 148 query( 149 'queryProcessAsyncFunc', 150 `SELECT 151 A.tid, 152 P.pid, 153 c.ts-${traceRange.startTs} as startTs, 154 c.dur, 155 c.cat, 156 c.id, 157 c.depth, 158 c.argsetid, 159 c.cookie 160 FROM 161 (SELECT id, ts, parent_id, dur, depth, argsetid, cookie, cat from callstack where cookie NOT NULL) c 162 LEFT JOIN thread A ON A.id = c.parent_id 163 LEFT JOIN process P ON P.id = A.ipid 164 WHERE 165 startTs NOT NULL;`, 166 {}, 167 { traceId: traceId } 168 ); 169 170export const queryProcessAsyncFuncCat = ( 171 traceRange: { 172 startTs: number; 173 endTs: number; 174 } 175): Promise<Array<unknown>> => 176 query( 177 'queryProcessAsyncFuncCat', 178 ` 179 select 180 A.tid, 181 P.pid, 182 c.cat as threadName, 183 c.name as funName, 184 c.ts-${traceRange.startTs} as startTs, 185 c.dur, 186 c.depth, 187 c.cookie 188 from 189 (select callid, name, ts, dur, cat, depth, cookie, parent_id from callstack where cookie not null and cat not null and parent_id is null) C 190 left join 191 thread A on A.id = C.callid 192 left join 193 process P on P.id = A.ipid 194 where 195 startTs not null 196 order by cat; 197 `, 198 {} 199 ); 200 201export const getMaxDepthByTid = (traceId?: string): //@ts-ignore 202 Promise<Array<unknown>> => 203 query( 204 'getMaxDepthByTid', 205 `SELECT 206 tid, 207 ipid, 208 maxDepth 209 FROM 210 thread T 211 LEFT JOIN ( 212 SELECT 213 callid, 214 MAX( c.depth + 1 ) AS maxDepth 215 FROM 216 callstack C 217 WHERE 218 c.ts IS NOT NULL 219 AND c.cookie IS NULL 220 GROUP BY 221 callid 222 ) C ON T.id = C.callid 223 WHERE 224 maxDepth NOT NULL`, 225 {}, 226 { traceId: traceId } 227 ); 228 229export const querySearchFuncData = ( 230 funcName: string, 231 tIds: number, 232 leftNS: number, 233 rightNS: number 234): Promise<Array<SearchFuncBean>> => 235 query( 236 'querySearchFuncData', 237 `select 238 c.ts - r.start_ts as startTime, 239 c.dur 240 from 241 callstack c 242 left join 243 thread t 244 on 245 c.callid = t.id 246 left join 247 process p 248 on 249 t.ipid = p.id 250 left join 251 trace_range r 252 where 253 c.name like '${funcName}%' 254 and 255 t.tid = ${tIds} 256 and 257 not ((startTime < ${leftNS}) or (startTime > ${rightNS})); 258 ` 259 ); 260 261export const queryFuncRowData = (funcName: string, tIds: number): Promise<Array<SearchFuncBean>> => 262 query( 263 'queryFuncRowData', 264 `select 265 c.name as funName, 266 c.ts - r.start_ts as startTime, 267 t.tid as tid 268 from 269 callstack c 270 left join 271 thread t 272 on 273 c.callid = t.id 274 left join 275 process p 276 on 277 t.ipid = p.id 278 left join 279 trace_range r 280 where 281 c.name like '${funcName}%' 282 and 283 t.tid = ${tIds} 284 `, 285 { $search: funcName } 286 ); 287 288export const fuzzyQueryFuncRowData = (funcName: string, tIds: number): 289 Promise<Array<SearchFuncBean>> => 290 query( 291 'fuzzyQueryFuncRowData', 292 `select 293 c.name as funName, 294 c.ts - r.start_ts as startTime, 295 c.ts - r.start_ts + c.dur as endTime, 296 t.tid as tid 297 from 298 callstack c 299 left join 300 thread t 301 on 302 c.callid = t.id 303 left join 304 process p 305 on 306 t.ipid = p.id 307 left join 308 trace_range r 309 where 310 c.name like '%${funcName}%' 311 and 312 t.tid = ${tIds} 313 `, 314 { $search: funcName } 315 ); 316 317export const getTabSlicesAsyncFunc = ( 318 asyncNames: string[], 319 asyncPid: number, 320 asyncTid: number | undefined, 321 leftNS: number, 322 rightNS: number 323): //@ts-ignore 324 Promise<Array<unknown>> => { 325 let condition = `${asyncTid !== null && asyncTid !== undefined ? `and A.tid = ${asyncTid}` : ''}`; 326 let sql = ` 327 SELECT 328 c.name AS name, 329 c.id, 330 sum( c.dur ) AS wallDuration, 331 count( c.name ) AS occurrences 332 FROM 333 (SELECT id, ts, parent_id, dur, name from callstack where cookie NOT NULL) C, 334 trace_range D 335 LEFT JOIN thread A ON A.id = C.parent_id 336 LEFT JOIN process P ON P.id = A.ipid 337 where 338 C.ts > 0 339 and 340 c.dur >= -1 341 and 342 P.pid = ${asyncPid} 343 and 344 c.name in (${asyncNames.map((it) => '\"' + it + '\"').join(',')}) 345 and 346 not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) ${condition} 347 group by 348 c.name 349 order by 350 wallDuration desc;`; 351 return query<SelectionData>('getTabSlicesAsyncFunc', sql, {}); 352}; 353 354export const getTabDetails = ( 355 asyncNames: Array<string>, 356 asyncPid: Array<number>, 357 funTids: Array<number>, 358 leftNS: number, 359 rightNS: number 360): //@ts-ignore 361 Promise<Array<unknown>> => { 362 let condition = ` 363 and A.tid in (${funTids!.join(',')}) 364 and c.cookie is null 365 ${`and P.pid in (${asyncPid.join(',')})`} 366 ${`and c.name in (${asyncNames.map((it) => '\"' + it + '\"').join(',')})`} 367 `; 368 let sql = ` 369 SELECT 370 c.name AS name, 371 c.dur AS duration, 372 c.id, 373 P.pid AS processId, 374 P.name AS process, 375 A.tid AS threadId, 376 A.name AS thread, 377 c.ts - D.start_ts as startNs 378 FROM 379 thread A,trace_range D 380 LEFT JOIN process P ON P.id = A.ipid 381 LEFT JOIN callstack C ON A.id = C.callid 382 where 383 C.ts > 0 384 and 385 c.dur >= -1 386 and 387 not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) ${condition} 388 `; 389 return query('getTabDetails', sql, {}); 390}; 391export const getSfDetails = ( 392 asyncNames: Array<string>, 393 asyncPid: number, 394 asyncTid: number | undefined, 395 leftNS: number, 396 rightNS: number 397): //@ts-ignore 398 Promise<Array<unknown>> => { 399 let condition = ` 400 and c.parent_id not null 401 ${asyncTid !== null && asyncTid !== undefined ? `and A.tid = ${asyncTid}` : ''} 402 ${`and P.pid = ${asyncPid}`} 403 ${`and c.name in (${asyncNames.map((it) => '\"' + it + '\"').join(',')})`} 404 `; 405 let sql = ` 406 SELECT 407 c.name AS name, 408 c.dur AS duration, 409 P.pid AS processId, 410 P.name AS process, 411 A.tid AS threadId, 412 A.name AS thread, 413 c.id, 414 c.ts - D.start_ts as startNs 415 FROM 416 (SELECT id, ts, parent_id, dur, name from callstack where cookie NOT NULL) C, 417 trace_range D 418 LEFT JOIN thread A ON A.id = C.parent_id 419 LEFT JOIN process P ON P.id = A.ipid 420 where 421 C.ts > 0 422 and 423 c.dur >= -1 424 and 425 not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) ${condition} 426 `; 427 return query('getSfDetails', sql, {}); 428}; 429export const getParentDetail = ( 430 asyncPid: Array<number>, 431 funTids: Array<number>, 432 leftNS: number, 433 rightNS: number): 434 Promise<Array<unknown>> => 435 query( 436 'getParentTime', 437 ` SELECT 438 C.ts - D.start_ts AS startTS, 439 C.ts - D.start_ts + C.dur AS endTS, 440 C.depth, 441 c.id, 442 c.name 443 FROM 444 thread A 445 JOIN trace_range D 446 LEFT JOIN process P ON P.id = A.ipid 447 LEFT JOIN callstack C ON A.id = C.callid 448 WHERE 449 C.ts > 0 450 AND C.dur >= - 1 451 AND NOT ( 452 ( C.ts - D.start_ts + C.dur < ${leftNS} ) 453 OR ( C.ts - D.start_ts > ${rightNS} ) 454 ) 455 AND C.cookie IS NULL 456 AND A.tid IN (${funTids!.join(',')}) 457 AND P.pid IN (${asyncPid.join(',')}) 458 ` 459 ); 460export const getFuncChildren = ( 461 funcIds: Array<number>, 462 asyncPid: Array<number>, 463 funTids: Array<number>, 464 leftNS: number, 465 rightNS: number, 466 isChild: boolean 467): //@ts-ignore 468 Promise<Array<unknown>> => { 469 let durStr = isChild ? 'C.dur AS duration,' : 'SUM(COALESCE(C.dur, 0)) AS duration,'; 470 let condition = isChild ? '' : 'group by parentName'; 471 let sql = ` 472 SELECT 473 c.parent_id parentId, 474 ${durStr} 475 c.id, 476 c.name, 477 c1.name parentName 478 FROM 479 thread A,trace_range D 480 LEFT JOIN process P ON P.id = A.ipid 481 LEFT JOIN callstack C ON A.id = C.callid 482 LEFT JOIN callstack C1 ON c.parent_id = C1.id 483 where 484 C.ts > 0 485 and 486 c.dur >= -1 487 and 488 not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) 489 and A.tid in (${funTids!.join(',')}) 490 and c.cookie is null 491 and P.pid in (${asyncPid.join(',')}) 492 and c.parent_id in (${funcIds.join(',')})${condition} 493 `; 494 return query('getTabDetails', sql, {}); 495}; 496export const getGhDetails = ( 497 asyncNames: Array<string>, 498 catName: string, 499 asyncPid: number, 500 leftNS: number, 501 rightNS: number 502): //@ts-ignore 503 Promise<Array<unknown>> => { 504 let sql = ` 505 SELECT 506 c.name AS name, 507 c.dur AS duration, 508 P.pid AS processId, 509 P.name AS process, 510 A.tid AS threadId, 511 A.name AS thread, 512 c.ts - D.start_ts as startNs 513 FROM 514 thread A,trace_range D 515 LEFT JOIN process P ON P.id = A.ipid 516 LEFT JOIN callstack C ON A.id = C.callid 517 where 518 C.ts > 0 519 and 520 c.dur >= -1 521 and 522 c.cookie not null 523 and 524 c.cat not null 525 and 526 c.parent_id is null 527 and 528 P.pid = ${asyncPid} 529 and 530 cat = '${catName}' 531 and 532 c.name in (${asyncNames.map((it) => '\"' + it + '\"').join(',')}) 533 and 534 not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) 535 `; 536 return query('getGhDetails', sql, {}); 537}; 538export const getTabSlicesAsyncCatFunc = ( 539 asyncCatNames: string, 540 asyncCatPid: number, 541 leftNS: number, 542 rightNS: number 543): Promise<Array<unknown>> => 544 query<SelectionData>( 545 'getTabSlicesAsyncCatFunc', 546 ` 547 select 548 c.name as name, 549 c.id, 550 sum(c.dur) as wallDuration, 551 count(c.name) as occurrences 552 from 553 thread A, trace_range D 554 left join process P on P.id = A.ipid 555 left join callstack C on A.id = C.callid 556 where 557 C.ts > 0 558 and 559 c.dur >= -1 560 and 561 c.cookie not null 562 and 563 c.cat not null 564 and 565 c.parent_id is null 566 and 567 P.pid = ${asyncCatPid} 568 and 569 c.cat = '${asyncCatNames}' 570 and 571 not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) 572 group by 573 c.name 574 order by 575 wallDuration desc;`, 576 { $leftNS: leftNS, $rightNS: rightNS } 577 ); 578 579export const querySearchFunc = (search: string): Promise<Array<SearchFuncBean>> => 580 query( 581 'querySearchFunc', 582 ` 583 select c.cookie, 584 c.id, 585 c.name as funName, 586 c.ts - r.start_ts as startTime, 587 c.dur, 588 c.depth, 589 t.tid, 590 t.name as threadName, 591 p.pid, 592 c.argsetid, 593 'func' as type 594 from callstack c left join thread t on c.callid = t.id left join process p on t.ipid = p.id 595 left join trace_range r 596 where c.name like '%${search}%' and startTime > 0 and cookie IS NULL; 597 `, 598 { $search: search }, 599 { traceId: Utils.currentSelectTrace } 600 ); 601 602 export const querySceneSearchFunc = (search: string, processList: Array<string>): 603 Promise<Array<SearchFuncBean>> => 604 query( 605 'querySceneSearchFunc', 606 `select c.cookie, 607 c.id, 608 c.name as funName, 609 c.ts - r.start_ts as startTime, 610 c.dur, 611 c.depth, 612 t.tid, 613 t.name as threadName, 614 p.pid, 615 c.argsetid, 616 'func' as type 617 from callstack c left join thread t on c.callid = t.id left join process p on t.ipid = p.id 618 left join trace_range r 619 where c.name like "%${search}%" ESCAPE '\\' and startTime > 0 and p.pid in (${processList.join(',')}) 620 and cookie IS NULL; 621 `, 622 { $search: search }, 623 { traceId: Utils.currentSelectTrace } 624 ); 625 626export const queryHeapFunction = (fileId: number): Promise<Array<HeapTraceFunctionInfo>> => 627 query( 628 'queryHeapFunction', 629 `SELECT 630 function_index as index , 631 function_id as id , 632 name, 633 script_name as scriptName, 634 script_id as scriptId, 635 line, 636 column 637 FROM js_heap_trace_function_info WHERE file_id = ${fileId}` 638 ); 639 640export const queryHeapTraceNode = ( 641 fileId: number 642): //@ts-ignore 643 Promise<Array<unknown>> => 644 query( 645 'queryHeapTraceNode', 646 `SELECT F.name, 647 F.script_name as scriptName, 648 F.script_id as scriptId, 649 F.column, 650 F.line, 651 N.id, 652 N.function_info_index as functionInfoIndex, 653 N.parent_id as parentId, 654 N.count, 655 N.size, 656 IFNULL( S.live_count, 0 ) AS liveCount, 657 IFNULL( S.live_size, 0 ) AS liveSize 658 FROM 659 js_heap_trace_node N 660 LEFT JOIN ( 661 SELECT 662 trace_node_id as traceNodeId, 663 SUM( self_size ) AS liveSize, 664 count( * ) AS liveCount 665 FROM 666 js_heap_nodes 667 WHERE 668 file_id = ${fileId} 669 AND trace_node_id != 0 670 GROUP BY 671 trace_node_id 672 ) S ON N.id = S.trace_node_id 673 LEFT JOIN js_heap_trace_function_info F ON (F.file_id = N.file_id 674 AND F.function_index = N.function_info_index) 675 WHERE 676 N.file_id = ${fileId} 677 ORDER BY 678 N.id` 679 ); 680 681export const queryTaskPoolOtherRelationData = (ids: Array<number>, tid: number): 682 Promise<Array<FuncStruct>> => { 683 let sqlStr = `select 684 c.ts-D.start_ts as startTs, 685 c.dur, 686 c.name as funName, 687 c.argsetid, 688 c.depth, 689 c.id as id, 690 A.itid as itid, 691 A.ipid as ipid 692 from thread A,trace_range D 693 left join callstack C on A.id = C.callid 694 where startTs not null and c.cookie is null and tid = $tid and c.id in (${ids.join(',')})`; 695 return query('queryTaskPoolOtherRelationData', sqlStr, { $ids: ids, $tid: tid }); 696}; 697 698export const queryTaskPoolRelationData = (ids: Array<number>, tids: Array<number>): 699 Promise<Array<FuncStruct>> => { 700 let sqlStr = `select 701 c.ts-D.start_ts as startTs, 702 c.dur, 703 c.name as funName, 704 c.argsetid, 705 c.depth, 706 c.id as id, 707 A.itid as itid, 708 A.ipid as ipid 709 from thread A,trace_range D 710 left join callstack C on A.id = C.callid 711 where startTs not null and c.cookie is null and c.id in (${ids.join(',')}) and tid in (${tids.join( 712 ',' 713 )})`; 714 return query('queryTaskPoolRelationData', sqlStr, { $ids: ids, $tids: tids }); 715}; 716 717export const queryStatesCut = (tIds: Array<number>, leftNS: number, rightNS: number): 718 Promise<Array<StateGroup>> => 719 query<StateGroup>( 720 'queryBinderByThreadId', 721 ` 722 select 723 B.id, 724 B.pid, 725 B.tid, 726 B.dur, 727 B.cpu, 728 B.state, 729 B.ts - C.start_ts AS ts, 730 B.dur + B.ts as endTs 731 from 732 thread_state AS B,trace_range AS C 733 where 734 B.tid in (${tIds.join(',')}) 735 and 736 ((B.ts + ifnull(B.dur,0) > ($leftStartNs + C.start_ts)) 737 and (B.ts + B.dur < ($rightEndNs + C.start_ts)) 738 or 739 ( 740 B.ts > ($leftStartNs + C.start_ts) and B.ts < ($rightEndNs + C.start_ts) 741 )) 742 order by 743 B.pid; 744 `, 745 { 746 $tIds: tIds, 747 $leftStartNs: leftNS, 748 $rightEndNs: rightNS, 749 } 750 ); 751 752export const queryLoopFuncNameCycle = ( 753 funcName: string, 754 tIds: string, 755 leftNS: number, 756 rightNS: number 757): Promise<Array<FuncNameCycle>> => 758 query( 759 'queryLoopFuncNameCycle', 760 ` 761 SELECT 762 c.name AS funcName, 763 c.ts - r.start_ts AS cycleStartTime, 764 0 AS cycleDur, 765 c.id, 766 t.tid, 767 p.pid 768 FROM 769 callstack c, trace_range r 770 LEFT JOIN 771 thread t 772 ON 773 c.callid = t.id 774 LEFT JOIN 775 process p 776 ON 777 t.ipid = p.id 778 WHERE 779 c.name like '${funcName}%' 780 AND 781 t.tid = ${tIds} 782 AND NOT 783 ((cycleStartTime < ${leftNS}) 784 OR 785 (cycleStartTime > ${rightNS})) 786 `, 787 { 788 $funcName: funcName, 789 $tIds: tIds, 790 $leftNS: leftNS, 791 $rightNS: rightNS, 792 } 793 ); 794 795export const querySingleFuncNameCycleStates = ( 796 funcName: string, 797 tIds: string, 798 leftNS: number, 799 rightNS: number 800): Promise<Array<FuncNameCycle>> => 801 query( 802 'querySingleFuncNameCycle', 803 ` 804 SELECT 805 c.name AS funcName, 806 c.ts - r.start_ts AS cycleStartTime, 807 c.dur AS cycleDur, 808 c.id, 809 t.tid, 810 p.pid, 811 c.ts - r.start_ts + c.dur AS endTime 812 FROM 813 callstack c, trace_range r 814 LEFT JOIN 815 thread t 816 ON 817 c.callid = t.id 818 LEFT JOIN 819 process p 820 ON 821 t.ipid = p.id 822 WHERE 823 c.name like '${funcName}%' 824 AND 825 t.tid = ${tIds} 826 AND NOT 827 ((cycleStartTime < ${leftNS}) 828 OR 829 (endTime > ${rightNS})) 830 `, 831 { 832 $funcName: funcName, 833 $tIds: tIds, 834 $leftNS: leftNS, 835 $rightNS: rightNS, 836 } 837 ); 838