안녕하세요, 저는 JMP 개발 운영팀의 시스템 엔지니어인 멜라니 드레이크입니다. 저희 팀은 JMP 실행 파일과 설치 프로그램을 빌드하고, 자동화된 테스트를 실행하는 등 JMP R&D를 지원하는 다양한 자동화 프로세스를 제공합니다.
오늘은 프로세스 로그에서 정보를 수집하고 JMP를 사용하여 자동화된 프로세스 중 하나의 문제를 파악하고 전반적인 상태를 모니터링하는 방법에 대해 설명하겠습니다. 프로세스는 많은 데이터를 생성합니다. 계측기 판독값, 제조 생산 라인의 측정값, 또는 상태 로그 등에서 수집될 수 있습니다. 데이터는 일반 텍스트 또는 XML, JSON, CSV와 같은 구조화된 텍스트로 저장될 수 있습니다. 이 모든 데이터는 문제를 찾고 해결할 수 있는 많은 기회를 제공합니다.
하지만 이 데이터는 끊임없이 생성되고 업데이트되는 여러 개의 개별 파일에 기록되는 경우가 많습니다. 오류를 발견하고 추적하는 것은 매우 어려울 수 있습니다. 매일 밤 진행되는 JSL 단위 테스트 스트림에 대해 이야기해 보겠습니다. 이 프로세스는 매일 많은 로그 파일을 생성합니다. 특정 오류를 추적하고 해결하는 것이 매우 어렵다는 것을 알게 되었습니다. 데이터는 넘쳐났지만, 데이터를 활용하는 것은 매우 어려웠습니다.
JSL 단위 테스트 스트림이란 무엇일까요? JMP의 매일 빌드를 사용하여 실행하는 JSL 회귀 테스트 모음입니다. 현재 개발 중인 모든 버전의 JMP에서 매일 밤 실행되며, 다양한 Windows 및 Macintosh 운영 체제 버전에서도 실행됩니다. 테스터는 이 야간 실행을 통해 JMP의 오류를 찾아 개발자가 신속하게 해결할 수 있도록 합니다. 특정 테스트 스트림이 성공적으로 실행되지 않으면 해당 테스트 결과를 얻을 수 없습니다.
여름 몇 주 동안 저희는 네 가지 버전의 JMP를 동시에 개발했습니다. 즉, 매일 밤 JMP와 운영 체제 버전을 조합하여 18개의 테스트 스트림을 실행해야 했습니다. 전체 스트림을 실행하는 데 8~12시간이 걸릴 수 있습니다. 각 Windows 테스트 스트림을 세 개의 스트림으로 나누어 동시에 실행함으로써 모든 Windows 테스트 결과를 훨씬 더 빨리 완료할 수 있도록 했습니다. 이를 통해 베이징의 테스트 팀은 같은 날 테스트 결과를 사용할 수 있습니다.
이 모든 것을 종합해 보면, 매일 36개의 개별 콘솔 로그를 살펴보고 있었습니다. 특정 오류가 언제 어디서 발생했는지 알아내려고 많은 시간을 허비할 수 있었고, 실제로 그렇게 했습니다. 서로 다른 데이터 모음을 관리하는 해결책은 이 모든 것을 한곳에 통합하는 것입니다. 물론 JMP 데이터 테이블이겠죠.
JMP를 사용하면 데이터를 시각화하고 분석하고, JMP Live를 사용하여 다른 사람들과 결과를 공유할 수 있습니다. 저희는 로그를 JMP 데이터 테이블에 수집했습니다. 그래프를 사용하여 모든 문제를 추적하고 해결했으며, JMP Live에 게시하여 상태를 지속적으로 모니터링할 수 있습니다. Macintosh와 Windows에서 로그 출력이 달랐기 때문에 두 개의 데이터 테이블이 생성되었습니다. 일부 오류는 흔했지만, 많은 오류가 특정 운영 체제에서만 발생했습니다.
먼저 일반적인 워크플로우를 간략하게 설명한 후, 스크립트를 살펴보고 프로세스를 시연해 보겠습니다. 첫 번째 단계는 데이터가 어디에 있는지, 그리고 JMP에 어떻게 읽어들일지 파악하는 것입니다. 로그 파일은 디스크나 서버에 저장할 수 있습니다. 웹에서 접근할 수도 있고, API나 데이터베이스 쿼리를 통해 가져올 수도 있습니다. 저희는 모든 자동화 프로세스를 실행하는 Jenkins 서버에서 웹 기반 UI를 통해 텍스트 파일에 접근합니다. HTTP 요청을 사용하여 정보를 가져온 다음, Parse JSON을 사용하여 사용 가능한 형식으로 정보를 가져왔습니다.
두 번째 단계는 JMP로 데이터를 가져오는 것입니다. 로그 파일부터 시작할 수 있으며, 여기에 추가할 다른 메타데이터가 있을 수도 있습니다. 전체 로그를 하나의 셀에 넣을 수도 있고, 여러 셀, 열 또는 행에 걸쳐 로그를 분산시킬 수도 있습니다. 이는 데이터의 종류와 분석 방식에 따라 달라집니다. 이 경우에는 스트림 실행당 하나의 로그를 사용합니다. 행당 하나의 스트림 실행과 하나의 전체 로그를 하나의 셀에 넣습니다. 이 외에도 다양한 메타데이터를 수집합니다.
세 번째 단계는 텍스트를 파싱하는 것입니다. Parse XML이나 Parse JSON을 사용하여 원시 정보를 얻을 수 있습니다. 원시 정보를 얻은 후에는 정규 표현식, JSL 패턴 매칭 또는 JSL 문자 함수를 사용하여 정보를 파싱할 수 있습니다. 수식 열, 지표 열 또는 여러 응답 열을 사용하여 해당 정보를 그래프에 사용할 수 있는 형식으로 수집할 수 있습니다.
저희의 경우, 위에 나열된 거의 모든 기능을 활용했습니다. 수식 열을 사용하여 메타데이터 열에 대한 구체적인 정보를 수집한 다음, 정규식과 JSL 문자 함수를 사용하여 실제 로그를 파싱했습니다. 또한, 지표 열과 다중 응답 열을 사용하여 정보를 수집하고 이를 바탕으로 멋진 그래프를 만들었습니다.
네 번째 단계는 데이터를 탐색하고 그래프로 표시하는 것입니다. JMP는 Graph Builder를 비롯한 다양한 그래프 작성 플랫폼을 제공합니다. 이러한 플랫폼을 사용하여 오류 패턴을 찾고, 오류 간의 상관관계를 파악하고, 시간 경과에 따른 오류를 추적할 수 있습니다. 저희는 Graph Builder를 광범위하게 활용했습니다. 상관관계를 확인하기 위해 히트맵을 만들었습니다. 막대 차트와 산점도를 사용하여 시간, JMP 버전, 운영 체제 버전에 따른 오류를 추적했습니다. 또한 누적 막대 차트와 히트맵을 사용하여 시간에 따른 스트림 상태와 누락된 실행을 확인했습니다. 나중에 몇 가지 예를 보여드리겠습니다.
마지막으로 5단계에서는 동료들과 그래프를 공유해야 합니다. 특정 대상에게 특정 결과를 제시할 수 있습니다. 가시성, 투명성, 그리고 팀워크를 위해 공유할 수 있습니다. 또한 비공개, 개인 정보 또는 기밀 데이터를 숨길 수도 있습니다. 저희의 경우, 데이터의 일부를 내부 JMP Live 서버에 게시합니다. 그래프에 필요한 정보만 게시합니다. 그래프가 게시되면 회의에서 논의하기 매우 쉽고, 전체 프로세스의 전반적인 상태를 지속적으로 모니터링하기도 매우 쉽습니다.
JMP 19에는 '열 태그'라는 새로운 기능이 있습니다. 이 프로젝트에서 매우 유용했습니다. 세 개의 열 태그를 사용하고 있는데, 하나는 특정 마일스톤의 타임스탬프를 태그하는 데, 다른 하나는 발견된 오류를 태그하는 데, 그리고 다른 하나는 그래프에 사용하는 열을 태그하는 데 사용됩니다. 이 모든 기능의 작동 방식은 잠시 후에 보여드리겠습니다.
왜 이렇게 했을까요? 우선 JSL 코드가 크게 간소화되기 때문입니다. 모든 열 이름을 하드 코딩하는 대신 특정 태그로 열을 참조할 수 있습니다. 또한, 예를 들어 발견된 오류를 업데이트할 때 열 이름을 다시 변경할 필요가 없습니다. 새 열에 태그를 지정하기만 하면 자동으로 반영됩니다. 이렇게 하면 필요한 데이터만 서브셋하고 게시하는 작업이 매우 간편해집니다.
이제 데모를 살펴보고 JSL 스크립트를 보여드리겠습니다. 작업을 시작하기 전에 몇 줄을 실행하여 모든 것을 설정합니다. 함수와 변수 정의가 포함된 스크립트 몇 개를 추가합니다. 기본 테이블이 저장될 폴더 경로를 설정합니다. 이 폴더에는 지금까지 수집한 모든 데이터가 저장됩니다. 그런 다음 구성 정보가 포함된 CSV 파일을 엽니다.
실제 CSV 파일에는 이보다 더 많은 열이 있지만, 이 프레젠테이션에서는 삭제한 내부 정보가 포함되어 있습니다. 각 프로젝트에 필요한 기본 정보는 각 테스트 스트림의 이름과, 해당 스트림의 운영 체제에 대한 모든 테스트 스트림에서 지금까지 수집한 모든 데이터를 보관하는 운영 체제별 기본 테이블입니다.
이제 데이터를 읽어와야 합니다. 각 JMP 버전과 OS 버전 조합은 별도의 Jenkins 프로젝트로, 각 페이지가 있습니다. 각 테스트 스트림 실행은 메인 페이지의 하위 페이지입니다. 첫 번째 프로젝트인 Sonoma 기반 JMP 19를 살펴보겠습니다. 반복 횟수를 1로 설정합니다. 그런 다음 각 작업의 이름과 해당 작업을 가져올 대상 테이블을 읽어옵니다.
먼저 HTTP 요청을 사용하여 메인 페이지의 JSON을 가져오는데, 여기에는 각 스트림 실행에 대한 URL 목록이 포함되어 있습니다. 이 요청을 Jenkins에 전송하고 JSON을 가져온 후, 이를 파싱하여 빌드 목록에 추가합니다. 여기서는 실시간으로 실행할 수 없으므로 목록을 생성하겠습니다. 목록이 매우 긴 것을 보실 수 있습니다. 이 목록에는 Macintosh Sonoma에서 JMP 19에 대해 지금까지 실행된 모든 테스트 스트림의 빌드 ID가 포함되어 있습니다.
하지만 우리는 이미 이 데이터의 많은 부분을 메인 테이블에 가지고 있고, 이미 가지고 있는 데이터를 읽고 처리하는 데 많은 시간을 낭비하고 싶지 않습니다.이 문제를 해결하기 위해 메인 테이블을 엽니다.여기에는 이 프로젝트의 결과뿐만 아니라 모든 Macintosh 결과가 있습니다.그러면 이 프로젝트에 대해 이미 가지고 있는 빌드 ID 목록을 얻습니다.다시 말해, Sonoma의 JMP 19와 일치하는 메인 테이블의 행에 있는 ID입니다.두 빌드 ID 목록을 모두 연관 배열로 바꾸어 새 목록에서 현재 ID를 뺍니다.연관 배열 수학 마법은 아직 처리하지 않은 실행의 짧은 목록을 제공합니다.395개 행 대신 3개 행을 처리한 다음 대부분을 버리는 것이 훨씬 쉽고 빠릅니다.
두 번째 단계는 해당 데이터를 JMP로 실제로 가져오는 것입니다. 목록이 비어 있지 않으면, 각 스트림 실행에서 원하는 데이터를 가져와 데이터 테이블로 가져오는 함수를 직접 작성합니다. 이 함수는 Parse JSON을 다시 사용하여 원하는 데이터를 추출하고 추적한 후, 각 테스트 스트림의 열과 행에 새 데이터 테이블에 추가합니다.
샘플 테이블을 열어서 어떤 모습인지 확인해 보겠습니다. 이미 일부 메타데이터가 입력되어 있는 것을 확인할 수 있습니다. 특정 스트림의 지속 시간, 이름, ID 번호, 빌드 상태, 성공 여부, 그리고 시작 날짜/시간 스탬프를 가져옵니다. 가장 중요한 것은 모든 정보를 가져올 텍스트 로그로 연결되는 URL을 확보했다는 것입니다. 다음으로, 이 테이블에 로그 파일 내용을 담을 열을 추가하고, load text file 명령을 사용하여 해당 URL에서 로그 파일을 읽어 테이블에 삽입합니다.
세 번째 단계는 이 행의 메타데이터와 콘솔 로그를 구문 분석하는 것입니다. 몇 가지 새로운 파생 열을 추가합니다. 예를 들어, 해당 기간을 분 단위로 측정된 실행 시간으로 변환하여 이해하기 쉽게 만듭니다. 또한 타임스탬프를 제외한 날짜만 있는 날짜 열을 추가합니다. 이렇게 하면 일부 그래프가 훨씬 더 쉬워집니다. 마지막으로 요일 열을 추가합니다. 이 열도 일부 그래프에 사용합니다.
다음으로 로그 분석 단계를 설정합니다. 특정 이정표의 시작 시간을 수집하기 위해 열을 추가합니다. Windows와 Macintosh에 따라 다른 시작 열을 정의하고, 이러한 열을 추가할 때 시작 태그를 지정합니다. 여기 녹색 태그가 보이는데, 이는 모든 열이 동일한 태그로 지정되었음을 나타냅니다. 아래에서 확인할 수 있습니다.
다음에 추가하는 열은 마지막으로 시작된 항목입니다. 오류로 인해 테스트 스트림이 실행을 중단하면 마지막으로 성공한 마일스톤을 가져올 수 있습니다. 이 행의 시작 태그가 있고 날짜/시간 스탬프가 있는 마지막 열이 됩니다. 프로세스가 VM 또는 서버와의 연결이 끊어지면 로그에 JavaScript 오류가 기록됩니다. 이러한 오류는 반드시 수집합니다.
마지막으로, 오류 정보를 수집하기 위해 다중 응답 열을 추가합니다. 이 부분은 잠시 후에 다시 설명하겠습니다. 다음 단계는 해당 로그를 실제로 구문 분석하는 것입니다. 하나의 루프를 사용하여 각 행에 대해 전체 콘솔을 구문 분석하고, 원하는 정보를 가져와 해당 행의 모든 새 열에 추가합니다. 콘솔 로그는 각 행마다 하나의 셀에 저장됩니다. 이를 문자열 목록으로 변환하는데, 로그의 각 줄이 문자열입니다.
그런 다음 해당 목록을 한 번 반복하고 각 줄을 찾고 있는 특정 항목, 시작 시간, 오류 등과 비교합니다. 이 방법은 추출하려는 각 항목에 대해 로그를 한 번씩 분석하는 것보다 훨씬 효율적입니다. 가장 먼저 찾는 것은 JavaScript 오류입니다. 이를 발견하면 오류를 가져와 데이터 테이블에 저장한 후 다음 행의 콘솔 로그로 이동합니다. 이렇게 작업이 중단되면 더 이상 얻을 수 있는 정보가 없습니다.
Jenkins 프로젝트에서는 특정 오류를 찾고, 그 검색 결과는 로그 끝에 출력되는데, 이를 텍스트 파인더라고 합니다. 텍스트 파인더의 첫 번째 인스턴스에 도달하면 로그 끝까지 수집합니다. 로그가 끝나는 지점에서 루프를 멈춥니다. 이 부분을 함수에 입력하는데, 잠시 후에 보여드리겠습니다.
이 두 가지 외에도, 이정표를 찾아 각 이정표에 대한 날짜/시간 스탬프를 표에 삽입합니다. 마지막으로, 루프가 끝나면 해당 텍스트 파인더를 수집합니다. 찾은 각 텍스트 파인더는 오류가 발견되지 않으면 0, 발견되면 1을 갖는 지표 열로 표에 추가됩니다.
그러면 해당 오류를 찾지 못한 부분이 누락됩니다. 예를 들어, 새로운 오류라면 이전에 찾지 않은 것입니다. 각 텍스트 찾기 열은 텍스트 찾기로 태그가 지정됩니다. 이러한 지표 열이 채워지면, 해당 행에 1이 포함된 텍스트 찾기로 태그가 지정된 각 열의 이름을 가져와서 다중 응답 열에 추가합니다. 이 열은 나중에 그래프 작성에 사용됩니다.
마지막으로 마지막 작업을 시작하여 이 행의 테이블에 추가합니다. 이후 행에 대한 루프는 찾은 텍스트 파인더를 이미 있는 열과 비교하여 아직 없는 텍스트 파인더만 추가합니다. 이 모든 작업을 실행한 후 테이블이 어떻게 보이는지 보여주는 예를 보여드리겠습니다. 처음에 수집한 모든 메타데이터가 아직 남아 있습니다. 로그 텍스트도 있고, 파생 데이터도 있습니다. 이제 모든 시작 열에 날짜/시간 스탬프가 포함되어 각 열이 시작된 정확한 시간을 알 수 있습니다.
마지막으로 시작한 작업이 마지막으로 성공한 항목을 수집했습니다. Java 연결 해제 오류는 발생하지 않았지만, 발생했다면 오류가 발생했을 것입니다.
마지막으로 모든 텍스트 찾기 열을 추가했습니다. 빨간색으로 표시된 것을 확인하실 수 있습니다. 모든 실행은 실제로 성공적이었지만, 오류에 대한 긍정적인 결과 몇 가지를 추가하여 다중 응답 열에서 어떻게 표시되는지 보여드리고자 합니다. 이 열에는 두 번의 실패가 있었고 세미콜론으로 구분되어 있습니다. 이 열에는 실패가 한 번만 있었습니다.
마지막 단계는 이 모든 새로운 정보를 기본 데이터 테이블에 추가하는 것입니다. 간단한 연결 작업입니다. 원래는 테이블에 새로운 세 행을 추가하려고 했습니다. 마지막 단계는 이 테이블을 저장하는 것입니다. 로그가 수백 줄에서 수천 줄에 달하는 경우도 있으므로 저장할 때 테이블을 압축합니다. 1,333개의 행은 엄청난 양의 데이터이고, 테이블은 매우 커질 수 있습니다. 이것이 이 과정의 핵심이며, 개발에 가장 오랜 시간이 걸린 부분입니다. 정확히 어떤 정보가 필요한지 결정하는 것은 반복적인 과정이었습니다.
마지막 단계는 데이터를 탐색하고 그래프로 표현하는 것입니다. 문제 진단에 도움이 되도록 디자인한 여러 그래프가 저장되어 있습니다. 그중 몇 가지를 보여드리겠습니다. 이 그래프는 날짜와 운영 체제에 따라 스트리밍 실행 시간을 보여줍니다.
짧은 시간 동안 스트리밍되는 스트림은 대개 빠르게 재생되다가 갑자기 중단됩니다. 이런 스트림에서는 결과가 거의 나오지 않습니다. 매우 오랜 시간 동안 스트리밍된 스트림은 어딘가에서 멈춰서 결국 시간이 초과될 가능성이 높습니다. 이러한 문제들은 모두 더 자세히 살펴봐야 할 문제입니다. 이번 스트리밍은 정말 안정적으로 재생됩니다. 항상 좋은 소식입니다.
또 다른 유용한 그래프는 JavaScript 오류와 마지막으로 성공적으로 시작된 항목 간의 상관 관계를 보여주는 그래프입니다. 문제 진단에 매우 유용할 수 있습니다. 제가 가장 좋아하는 그래프는 아마도 이 마지막 그래프일 겁니다. 실행이 누락된 부분의 빌드 상태를 보여주는 히트맵입니다.
테스트 스트림을 주 5일 운영하고 있다는 것을 바로 알 수 있습니다. 주말에는 운영하지 않습니다. 이 스트림에는 테스트 스트림을 전혀 운영하지 않아 실행이 누락된 부분이 많습니다. 테스트 스트림은 실행되었지만 오류가 있었습니다. 녹색으로 표시된 모든 실행은 아무런 문제가 없었습니다. 방금 이 표에 추가한 세 행은 다음과 같습니다.
다섯 번째 단계는 그래프를 공유하는 것입니다. 이 모든 그래프는 JMP Live에 이미 게시되어 있으므로 JMP Live에서 테이블과 그래프를 업데이트하기만 하면 됩니다. JMP Live 인스턴스에 대한 연결을 생성합니다. 이 연결은 file, publish, manage connections에 정의되어 있습니다. 데이터 테이블을 열고 subset 스크립트를 실행합니다.
왜 굳이 이렇게까지 하는지 궁금하실 겁니다. 원래 메인 테이블에는 열이 39개였습니다. 하위 집합 테이블에는 열이 9개뿐인데, 이 9개 열에는 거대한 콘솔 로그가 포함되지 않습니다. 덕분에 하위 집합 테이블의 크기가 훨씬 작아져 JMP Live의 성능이 향상됩니다. 또한, 비공개 또는 독점 데이터를 쉽게 게시하지 않아도 된다는 점도 좋습니다. 사용하는 그래프에 꼭 필요한 데이터만 게시하고 있으니까요.
해당 하위 집합 테이블과 JMP Live 연결 등의 정보를 테이블의 모든 그래프를 순회하는 함수로 전송합니다. 이 함수는 모든 테이블을 업데이트합니다. JMP Live의 모든 그래프에서 하위 집합 테이블을 업데이트한 후, 다음 테이블(이 경우에는 Windows)로 이동합니다. 이것으로 작업이 완료됩니다. 이 작업을 매일 실행하면 새로운 결과를 얻을 수 있습니다.
JMP Live에 게시한 일부 그래프 샘플입니다. 대시보드에서 이러한 그래프와 다른 그래프를 통해 전반적인 추세와 문제점을 매우 쉽게 파악할 수 있습니다. JMP Live이므로 각 그래프를 개별적으로 살펴보고 추가 정보를 확인할 수 있습니다. 모든 그래프는 로컬 데이터 필터를 적용하여 게시합니다. 이를 통해 특정 기간 동안 실행된 특정 테스트 스트림만 확인할 수 있습니다. 예를 들어, 지난 몇 주 동안 실행된 JMP 20 테스트 스트림만 확인할 수 있습니다.
결국, 우리는 무엇을 이루었을까요? 바로 인사이트를 자동화한 것입니다. 그래프를 보고 무슨 일이 일어나고 있는지 확인하기만 하면 됩니다. 그래프는 자동으로 업데이트됩니다. 특정 오류를 일일이 관리할 필요가 없습니다. 자동으로 게시됩니다. 모든 것이 미리 정의되어 있기 때문에 프로세스가 매일 원활하게 진행됩니다. 데이터를 분석하면서 무엇을 추적해야 할지 결정했습니다. 콘솔 로그에 출력하는 내용을 변경했습니다. 예를 들어 마일스톤 시작 시간을 추가하는 등의 작업을 했습니다. 해결해야 할 문제를 보여주는 데 가장 적합한 그래프를 결정했습니다.
이 스크립트를 완성하고 실행할 때쯤에는 큰 성과를 거두었습니다. 지금까지 모든 문제를 해결했고, 테스트 스트림이 정상적으로 실행되는 경우가 많아 그래프도 더 이상 흥미롭지 않습니다. 감사합니다.