1"use strict";(globalThis.webpackChunkdocs=globalThis.webpackChunkdocs||[]).push([[3227],{48342(e,n,r){r.r(n),r.d(n,{assets:()=>d,contentTitle:()=>l,default:()=>h,frontMatter:()=>t,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"plugins/chronostream","title":"ChronoStream","description":"ChronoStream is a streaming telemetry plugin for ChronoLog. It reads log events from ChronoLog stories in real time and forwards them to a visualization backend (currently InfluxDB + Grafana), giving operators a live dashboard of system metrics without modifying the producing application.","source":"@site/versioned_docs/version-3.1.1/plugins/chronostream.md","sourceDirName":"plugins","slug":"/plugins/chronostream","permalink":"/docs/3.1.1/plugins/chronostream","draft":false,"unlisted":false,"editUrl":"https://github.com/grc-iit/ChronoLog/tree/main/docs/versioned_docs/version-3.1.1/plugins/chronostream.md","tags":[],"version":"3.1.1","sidebarPosition":3,"frontMatter":{"sidebar_position":3,"title":"ChronoStream"},"sidebar":"pluginsSidebar","previous":{"title":"ChronoKVS","permalink":"/docs/3.1.1/plugins/chronokvs"},"next":{"title":"ChronoViz","permalink":"/docs/3.1.1/plugins/chronoviz"}}');var i=r(74848),o=r(28453);const t={sidebar_position:3,title:"ChronoStream"},l="ChronoStream",d={},c=[{value:"Architecture",id:"architecture",level:2},{value:"Key Components",id:"key-components",level:3},{value:"Prerequisites",id:"prerequisites",level:2},{value:"Setup",id:"setup",level:2},{value:"1. Environment Variables",id:"1-environment-variables",level:3},{value:"2. Build and Install ChronoLog",id:"2-build-and-install-chronolog",level:3},{value:"3. Launch Grafana and InfluxDB",id:"3-launch-grafana-and-influxdb",level:3},{value:"4. Start ChronoLog",id:"4-start-chronolog",level:3},{value:"Usage",id:"usage",level:2},{value:"Writing Metrics",id:"writing-metrics",level:3},{value:"Streaming to Grafana",id:"streaming-to-grafana",level:3},{value:"Viewing Dashboards",id:"viewing-dashboards",level:3},{value:"Roadmap",id:"roadmap",level:2}];function a(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,o.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"chronostream",children:"ChronoStream"})}),"\n",(0,i.jsxs)(n.p,{children:["ChronoStream is a ",(0,i.jsx)(n.strong,{children:"streaming telemetry plugin"})," for ChronoLog. It reads log events from ChronoLog stories in real time and forwards them to a visualization backend (currently InfluxDB + Grafana), giving operators a live dashboard of system metrics without modifying the producing application."]}),"\n",(0,i.jsx)(n.h2,{id:"architecture",children:"Architecture"}),"\n",(0,i.jsx)(n.p,{children:"ChronoStream uses two cooperating client applications and an external time-series stack:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 client_writer \u2502\u2500\u2500\u2500\u2500>\u2502 ChronoLog \u2502\u2500\u2500\u2500\u2500>\u2502client_reader \u2502\u2500\u2500\u2500\u2500>\u2502 InfluxDB \u2502\u2500\u2500\u2500\u2500>\u2502 Grafana \u2502\n\u2502 (producer) \u2502 \u2502 Cluster \u2502 \u2502_stream_influx \u2502 \u2502 \u2502 \u2502 Dashboard \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n"})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"client_writer"})})," \u2014 collects host metrics (CPU, memory, network) from ",(0,i.jsx)(n.code,{children:"/proc"})," and writes them as log events into ChronoLog stories."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"client_reader_stream_influx"})})," \u2014 polls ChronoLog stories on a configurable interval, converts events to InfluxDB line protocol, and pushes them to InfluxDB in batches. It maintains a per-story curs
1or to avoid re-reading events."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"InfluxDB + Grafana"})," \u2014 InfluxDB stores the time-series data; a pre-provisioned Grafana dashboard renders it with filtering by chronicle, story, and time range."]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"key-components",children:"Key Components"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Component"}),(0,i.jsx)(n.th,{children:"Path"}),(0,i.jsx)(n.th,{children:"Description"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"client_writer"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"ClientScripts/client_writer.cpp"})}),(0,i.jsxs)(n.td,{children:["Reads CPU/memory/network from ",(0,i.jsx)(n.code,{children:"/proc"}),", writes to ChronoLog at a configurable interval"]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"client_reader_stream_influx"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"ClientScripts/client_reader_stream_influx.cpp"})}),(0,i.jsx)(n.td,{children:"Replays stories from ChronoLog and streams events to InfluxDB via HTTP"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"InfluxDBSink"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"StreamingScripts/InfluxDBSink.{h,cpp}"})}),(0,i.jsx)(n.td,{children:"HTTP sink that POSTs line protocol to InfluxDB v2 with token auth, timeouts, and retries"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"StreamSink"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"StreamingScripts/StreamSink.h"})}),(0,i.jsx)(n.td,{children:"Generic interface for streaming backends"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Transform"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"StreamingScripts/Transform.{h,cpp}"})}),(0,i.jsxs)(n.td,{children:["Converts ChronoLog events into ",(0,i.jsx)(n.code,{children:"TelemetryB
1atch"})," line protocol for InfluxDB"]})]})]})]}),"\n",(0,i.jsx)(n.h2,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"A running ChronoLog service (all four processes: Visor, Grapher, Player, Keeper)"}),"\n",(0,i.jsx)(n.li,{children:"Docker and Docker Compose (for Grafana and InfluxDB)"}),"\n",(0,i.jsx)(n.li,{children:"ChronoLog client library installed"}),"\n",(0,i.jsx)(n.li,{children:"Spack environment activated (for building)"}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"setup",children:"Setup"}),"\n",(0,i.jsx)(n.h3,{id:"1-environment-variables",children:"1. Environment Variables"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:'cd <ChronoLog repo>\nsource ~/<spack-directory>/spack/share/spack/setup-env.sh\nspack env activate .\n\nexport VM_IP=$(hostname -I | awk \'{print $1}\')\nexport INSTALL_PREFIX=~/chronolog-install\nexport INSTALL_DIR="$INSTALL_PREFIX/chronolog"\nexport SRC_ROOT=~/Desktop/ChronoLog\nexport LD_LIBRARY_PATH="$INSTALL_DIR/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"\nexport INFLUX_ORG=chronolog\nexport INFLUX_BUCKET=telemetry\nexport INFLUX_TOKEN=chronolog-dev-token-123\nexport INFLUX_URL="http://$VM_IP:8086/api/v2/write?org=$INFLUX_ORG&bucket=$INFLUX_BUCKET&precision=ns"\n'})}),"\n",(0,i.jsx)(n.admonition,{type:"note",children:(0,i.jsxs)(n.p,{children:["After setting ",(0,i.jsx)(n.code,{children:"VM_IP"}),", update the IP address in the InfluxDB datasource provisioning file at ",(0,i.jsx)(n.code,{children:"plugins/chrono-stream/GrafanaInfluxSetup/provisioning/datasources/datasource.yml"}),"."]})}),"\n",(0,i.jsx)(n.h3,{id:"2-build-and-install-chronolog",children:"2. Build and Install ChronoLog"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"cd $SRC_ROOT/tools/deploy\n./local_single_user_deploy.sh -b -I $INSTALL_PREFIX\n./local_single_user_deploy.sh -i -I $INSTALL_PREFIX\n"})}),"\n",(0,i.jsx)(n.h3,{id:"3-launch-grafana-and-influxdb",children:"3. Launch Grafana and InfluxDB"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"cd plugins/chrono-stream/GrafanaInfluxSetup/\ndocker compose --env-file .env up -d\n"})}),"\n",(0,i.jsx)(n.p,{children:"Verify the containers are running:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"docker ps\n"})}),"\n",(0,i.jsxs)(n.p,{children:["You should see both ",(0,i.jsx)(n.code,{children:"grafana"})," (port 3000) and ",(0,i.jsx)(n.code,{children:"influxdb"})," (port 8086) containers."]}),"\n",(0,i.jsx)(n.p,{children:"To stop and clean up:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"docker compose down\n# Optional: delete the volume for a full refresh\ndocker volume rm $(docker volume ls -q | grep grafana-data)\n"})}),"\n",(0,i.jsx)(n.h3,{id:"4-start-chronolog",children:"4. Start ChronoLog"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"./local_single_user_deploy.sh -d -w $INSTALL_DIR\n"})}),"\n",(0,i.jsx)(n.p,{children:"Confirm all four processes are running:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"ps -ef | grep chrono\n"})}),"\n",(0,i.jsxs)(n.p,{children:["You should see ",(0,i.jsx)(n.code,{children:"chrono-visor"}),", ",(0,i.jsx)(n.code,{children:"chrono-grapher"}),", ",(0,i.jsx)(n.code,{children:"chrono-player"}),", and ",(0,i.jsx)(n.code,{children:"chrono-keeper"}),"."]}),"\n",(0,i.jsx)(n.p,{children:"To stop ChronoLog:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"./local_single_user_deploy.sh -s -w $INSTALL_DIR\n"})}),"\n",(0,i.jsx)(n.h2,{id:"usage",children:"Usage"}),"\n",(0,i.jsx)(n.h3,{id:"writing-metrics",children:"Writing Metrics"}),"\n",(0,i.jsx)(n.p,{children:"Start the client writer to collect and log host metrics:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:'export CHRONICLE=grafana_chronicle\n$INSTALL_DIR/bin/client_writer \\\n -c $INSTALL_DIR/conf/default-chrono-client-conf.json \\\n -r "$CHRONICLE" \\\n -d 120 \\\n -i 5\n'})}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Flag"}),(0,i.jsx)(n.th,{children:"Description"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"-c"})}),(0,i.jsx)(n.td,{children:"Path to client configuration file"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"-r"})}),(0,i.jsx)(n.td,{children:"Chronicle name"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"-d"})}),(0,i.jsx)(n.td,{children:"Duration in seconds"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"-i"})}),(0,i.jsx)(n.td,{children:"Sampling interval in seconds"})]})]})]}),"\n",(0,i.jsxs)(n.p,{children:["The writer creates three stories under the chronicle: ",(0,i.jsx)(n.code,{children:"cpu_usage"}),", ",(0,i.jsx)(n.code,{children:"memory_usage"}),", and ",(0,i.jsx)(n.code,{children:"network_usage"}),"."]}),"\n",(0,i.jsx)(n.h3,{id:"streaming-to-grafana",children:"Streaming to Grafana"}),"\n",(0,i.jsx)(n.p,{children:"After the writer has been running for 30-45 seconds, start the reader to stream events through InfluxDB to Grafana:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:'$INSTALL_DIR/bin/client_reader_stream_influx \\\n -c $INSTALL_DIR/conf/default-chrono-client-conf.json \\\n -r "$CHRONICLE" \\\n -s cpu_usage,memory_usage,network_usage \\\n --window-sec 300 \\\n --poll-interval-sec 5 \\\n --influx-url "$INFLUX_URL" \\\n --influx-token "$INFLUX_TOKEN"\n'})}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Flag"}),(0,i.jsx)(n.th,{children:"Description"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"-c"})}),(0,i.jsx)(n.td,{children:"Path to client configuration file"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"-r"})}),(0,i.jsx)(n.td,{children:"Chronicle name"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"-s"})}),(0,i.jsx)(n.td,{children:"Comma-separated list of story names to stream"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"--window-sec"})}),(0,i.jsx)(n.td,{children:"Replay window size in seconds"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"--poll-interval-sec"})}),(0,i.jsx)(n.td,{children:"Polling interval in seconds"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"--influx-url"})}),(0,i.jsx)(n.td,{children:"InfluxDB v2 write endpoint URL"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"--influx-token"})}),(0,i.jsx)(n.td,{children:"InfluxDB authentication token"})]})]})]}),"\n",(0,i.jsxs)(n.p,{children:["To stop the reader, press ",(0,i.jsx)(n.code,{children:"Ctrl+Z"})," and then terminate the process:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"ps -ef | grep client_reader_stream_influx\nkill -KILL <pid>\n"})}),"\n",(0,i.jsx)(n.h3,{id:"viewing-dashboards",children:"Viewing Dashboards"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["Open Grafana at ",(0,i.jsx)(n.code,{children:"http://<VM_IP>:3000/"})," (default credentials: ",(0,i.jsx)(n.code,{children:"admin"})," / ",(0,i.jsx)(n.code,{children:"admin"}),")."]}),"\n",(0,i.jsxs)(n.li,{children:["Navigate to ",(0,i.jsx)(n.strong,{children:"Dashboards > ChronoLog > ChronoLog Telemetry"}),"."]}
1),"\n",(0,i.jsx)(n.li,{children:"Use the dashboard filters to select a chronicle name, story name, or time range, then click refresh."}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"roadmap",children:"Roadmap"}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsx)(n.p,{children:"The items below describe planned improvements to ChronoStream."})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Direct data source (v2)"})," \u2014 Replace InfluxDB with a native ChronoLog data source in Grafana (see ",(0,i.jsx)(n.a,{href:"/docs/3.1.1/plugins/chronoviz",children:"ChronoViz"}),"), removing the dependency on a third-party time-series store."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Automatic push (v3)"})," \u2014 Stream events directly from ChronoLog to Grafana as they are written, eliminating the need for the explicit reader application."]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(a,{...e})}):a(e)}},28453(e,n,r){r.d(n,{R:()=>t,x:()=>l});var s=r(96540);const i={},o=s.createContext(i);function t(e){const n=s.useContext(o);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:t(e.components),s.createElement(o.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.