diff --git a/src/infuse_iot/rpc_wrappers/thread_stats.py b/src/infuse_iot/rpc_wrappers/thread_stats.py index feb6cf4..c4b8d4a 100644 --- a/src/infuse_iot/rpc_wrappers/thread_stats.py +++ b/src/infuse_iot/rpc_wrappers/thread_stats.py @@ -15,7 +15,8 @@ class thread_stats(InfuseRpcCommand, defs.thread_stats): @classmethod def add_parser(cls, parser): sort = parser.add_mutually_exclusive_group() - sort.add_argument("--sort-stack", action="store_true", help="Sort threads by stack usage (%)") + sort.add_argument("--sort-percentage", action="store_true", help="Sort threads by stack usage (%)") + sort.add_argument("--sort-bytes", action="store_true", help="Sort threads by bytes bytes") def __init__(self, args): self.args = args @@ -36,18 +37,22 @@ def data_recv_cb(self, offset: int, data: bytes) -> None: name = data[:name_len].decode() data = data[name_len:] percent = int(100 * base.stack_used / base.stack_size) - self._info.append((name, base.stack_used, base.stack_size, percent, base.utilization)) + unused = base.stack_size - base.stack_used + self._info.append((name, base.stack_used, unused, base.stack_size, percent, base.utilization)) def handle_response(self, return_code, response): if return_code != 0: print(f"Failed to query thread stats ({errno.strerror(-return_code)})") return - if self.args.sort_stack: - # Sort by stack usage - display = sorted(self._info, key=lambda x: x[3], reverse=True) + if self.args.sort_percentage: + # Sort by stack usage percent + display = sorted(self._info, key=lambda x: x[4], reverse=True) + elif self.args.sort_bytes: + # Sort by stack bytes free + display = sorted(self._info, key=lambda x: x[2], reverse=True) else: display = self._info - headings = ["Thread", "Stack Used", "Stack Size", "Stack %", "CPU %"] + headings = ["Thread", "Stack Used", "Stack Free", "Stack Size", "Stack %", "CPU %"] print(tabulate.tabulate(display, headers=headings)) diff --git a/src/infuse_iot/tools/csv_plot.py b/src/infuse_iot/tools/csv_plot.py index 9f95f6a..199dcc3 100644 --- a/src/infuse_iot/tools/csv_plot.py +++ b/src/infuse_iot/tools/csv_plot.py @@ -16,31 +16,56 @@ class SubCommand(InfuseCommand): @classmethod def add_parser(cls, parser): - parser.add_argument("--file", "-f", required=True, type=ValidFile) + parser.add_argument( + "--files", + "-f", + required=True, + type=ValidFile, + nargs="+", + ) parser.add_argument("--start", type=str, default="2024-01-01", help="Display data after") + parser.add_argument("--field", type=str, help="Single column to plot") + parser.add_argument("--group", action="store_true", help="Group all lines onto a single plot") def __init__(self, args): - self.file = args.file + self.files = args.files + self.field = args.field self.start = args.start + self.group = args.group def run(self): import pandas as pd import plotly.express as px + import plotly.graph_objects as go from dash import Dash, dcc, html - df = pd.read_csv(self.file) + fig: None | go.Figure = None + figures: list[dcc.Graph] = [] + if self.group: + fig = go.Figure() - mask = df["time"] >= self.start - filtered_df = df.loc[mask] + for file in self.files: + df = pd.read_csv(file) - fig = px.line( - filtered_df, - x="time", - y=filtered_df.columns.values[1:], - title=str(self.file), - ) + mask = df["time"] >= self.start + filtered_df = df.loc[mask] + + y_data = filtered_df[self.field] if self.field else filtered_df.columns.values[1:] + if self.group: + assert fig is not None + fig.add_trace(go.Scatter(x=filtered_df["time"], y=y_data, name=str(file.name), mode="lines")) + else: + fig = px.line( + filtered_df, + x="time", + y=y_data, + title=str(file), + ) + figures.append(dcc.Graph(figure=fig)) app = Dash() - app.layout = html.Div([dcc.Graph(figure=fig)]) + if self.group: + figures = [dcc.Graph(figure=fig, style={"height": "90vh"})] + app.layout = html.Div(figures) app.run(debug=True)