diff --git a/.gitmodules b/.gitmodules index d1c3f76..b4a771b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,7 +4,7 @@ branch = main [submodule "third-party/opus"] path = third-party/opus - url = https://github.com/xiph/opus.git + url = https://github.com/xiph/opus [submodule "third-party/speex"] path = third-party/speex - url = https://github.com/xiph/speex.git + url = https://github.com/xiph/speex/ diff --git a/README.md b/README.md index 38db4fd..10afb5a 100644 --- a/README.md +++ b/README.md @@ -98,3 +98,15 @@ Activities bind via AIDL for status and (on the receiver) attaching the video `S ## Development (git flow) Green **`master`** plus integration branch **`next`**. See [docs/GIT_FLOW.md](docs/GIT_FLOW.md). + +## PDF helpers + +Generate the GraphVis decision memo PDF (from `tmp/GRAFANA_vs_others_graphvis_pivot.md` source): + +```bash +bash scripts/build-graphvis-pdf.sh +``` + +Output: + +`tmp/GRAFANA_vs_others_graphvis_pivot.pdf` diff --git a/20260521_next_gen_steps_egress_review.pdf b/docs/20260521_next_gen_steps_egress_review.pdf similarity index 100% rename from 20260521_next_gen_steps_egress_review.pdf rename to docs/20260521_next_gen_steps_egress_review.pdf diff --git a/AV_QUALITY_QA_SESSION.md b/docs/AV_QUALITY_QA_SESSION.md similarity index 100% rename from AV_QUALITY_QA_SESSION.md rename to docs/AV_QUALITY_QA_SESSION.md diff --git a/docs/GRAFANA_vs_others_graphvis_pivot.md b/docs/GRAFANA_vs_others_graphvis_pivot.md new file mode 100644 index 0000000..81e1d6c --- /dev/null +++ b/docs/GRAFANA_vs_others_graphvis_pivot.md @@ -0,0 +1,82 @@ +# Android Cast — Graph Visualization Pivot + +_Date: 2026-06-02_ + +## Context and constraints + +- Stack: minimalistic LAMP with nginx + php-fpm + MariaDB. +- Main constraint: **no data duplication/ETL pipeline** (no cron-based mirroring or parallel stores). +- Need role-hierarchical analytics: user → slug admin → platform admin. + +## Executive summary + +- **Grafana is viable** if it can query MariaDB directly and enforce tenant/role boundaries safely. +- **Custom PHP dashboards are safer initially** for auth/session reuse and strict slug scoping. +- Recommended approach: **hybrid**. + - Define a canonical SQL metrics contract once. + - Ship custom BE dashboards first. + - Add Grafana later only if direct DB + RBAC integration is clean. + +## Option comparison + +| Option | Pros | Cons / Risks | Stack impact | +|---|---|---|---| +| Grafana (direct DB) | Fast polished charts, panel ecosystem, alerting | Auth/RBAC integration effort; slug filtering discipline required; one more service to operate | Adds Grafana runtime (no ETL required) | +| Custom PHP + nginx | Native shared login/session, direct BE internals, deterministic tenant logic | More UI/chart development effort | No new runtime dependencies | +| Hybrid (recommended) | Fast practical delivery + future flexibility | Needs shared metrics contract discipline | Minimal immediate risk | + +## Role-based graph packs (suggested) + +### User graphs + +- Unique devices per day (registrations/device IDs). +- Avg cast duration by day/week. +- Avg receive sessions by day/week. +- Success ratio (session start vs completed). +- App version distribution. + +### Slug admin graphs + +- Everything from user level, aggregated by slug. +- Active vs passive users/devices. +- Avg app bandwidth (send/recv) in 1d/7d windows. +- Install source pie: Play Market vs OTA vs custom. +- NTP/time-source usage split and correction savings metric. + +### Platform admin graphs + +- Everything from slug admin level, cross-slug view. +- Crashes per slug/device/user over time. +- Crash trend by app version / Android API / fingerprint. +- Crash-to-ticket linkage counts with issue tracker links. +- Top unreliable slugs/devices (rate-based). + +## Estimates (engineering) + +| Track | Initial delivery | Hardening | Total | +|---|---:|---:|---:| +| Grafana direct DB | 4-7 dev days | 5-8 dev days | 9-15 dev days | +| Custom PHP dashboards | 5-9 dev days | 3-6 dev days | 8-15 dev days | +| Hybrid phase-1 PHP then optional Grafana | 6-10 dev days | +4-7 dev days (optional) | 10-17 dev days | + +_Assumptions: current MariaDB crash/ticket/session structures are available; no major schema rewrite._ + +## Recommendation under your constraints + +1. Keep **single source of truth** in MariaDB (no ETL copy pipeline). +2. Build **SQL metrics views/contracts** first. +3. Implement **custom PHP dashboards** first for guaranteed auth and tenant correctness. +4. Add Grafana later only if direct DB + RBAC mapping is validated without complexity growth. + +## Implementation slice (proposed) + +1. Define metrics dictionary + SQL views by role scope (1-2 days). +2. Extend heartbeat with `time_source` + NTP correction savings fields (0.5-1 day). +3. Build user + slug-admin dashboard pages with 1d/7d selectors (3-5 days). +4. Build platform admin reliability board with crash/ticket links (2-3 days). +5. Optional Grafana POC on same views (2-3 days). + +## Source linkage + +- This markdown file is the editable source for: + - `tmp/GRAFANA_vs_others_graphvis_pivot.pdf` diff --git a/docs/GRAFANA_vs_others_graphvis_pivot.pdf b/docs/GRAFANA_vs_others_graphvis_pivot.pdf new file mode 100644 index 0000000..186a9db --- /dev/null +++ b/docs/GRAFANA_vs_others_graphvis_pivot.pdf @@ -0,0 +1,93 @@ +%PDF-1.4 +%“Œ‹ž ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R /F2 3 0 R +>> +endobj +2 0 obj +<< +/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font +>> +endobj +3 0 obj +<< +/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font +>> +endobj +4 0 obj +<< +/Contents 9 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +5 0 obj +<< +/Contents 10 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +6 0 obj +<< +/PageMode /UseNone /Pages 8 0 R /Type /Catalog +>> +endobj +7 0 obj +<< +/Author (Android Cast project) /CreationDate (D:20260602143110+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260602143110+02'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (\(unspecified\)) /Title (Grafana vs Others GraphVis Pivot) /Trapped /False +>> +endobj +8 0 obj +<< +/Count 2 /Kids [ 4 0 R 5 0 R ] /Type /Pages +>> +endobj +9 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2837 +>> +stream +Gb!Sm=``=W&q9SYkXT2.;7oK>:J0snZUsN9b1&P`.Ku#tKU>gH;RY`>DrVrcGt_/)h0mq?'?_X>5bHrcJ/kf`a-"-#*8pJkVJdJo>Z`PnnLYgu+5qTeo&I[9P!NX3Xr"Y\Ks;A;JWKDAfWo#=$TtZ+=_L;[=Ae#%HjAre$*NN[D/d<2[rB0?`>5U,2@(EZ8+8AiRO0%m+/XkeQ6)a'&^?CBnIo01+pNF])94'muO$\qHDHXB=um[j8MZ7660tKTn:rd^I;#U,9#T75Iuk1"9c(-b"cX'M(NqD8R!(FgaE-]GF_b+3-_DI=X)V;!g5-8+$MONCb;U\JU@R\)Q&._#tip$Hq=G;=W,WfMBXlE^:cgJ*L2,nA=-K=k=a%GJGPnhl[HaX-7l`e0/!8M-uD$17G%d+k[NLgq^4J^bN-EOK>:*ZF1J6[WA%p/ljKmObO?^VBoO&%4=(5F.\a/+]O&3b+bR?c+p>_QrFg0QM/4K>t]]`;12_+L-sD[VTe#ir?[ab?f.0F:3kaBC,'hS?!?)$^rs/]L*EY[1jLs,G;Xk3s#g=iiSVdi9l'`F,nP"4ppWl2S<`Q@U+#rsnc/=s.s<-Z=*jdioa[Z)mS0,4]:ojt)-k@YK7Uqlp7C368C'U(-ME5]h,&JQ'(6%AK7Ru#i#`C&]7E=:bk@&Q;uRKKk+P`B9-9NFZ<2YsKoa("#R$9&"F_??oSsbt6YHCYc:3ftk79C`)n)HUgH6kVFr9'6G^mL,K^k25`(#W/,XL0o]1s^jh)J8BEgLdu0QNX[.#VmLC^I1oKG_ZNdLAQ8ERRHN'eJAX"EWY[W;3QGW=f`KX:RC[&9>.AFgSELIT,)LbfR>lj\L$)GO_&KN93]4AueMD\.S=6\bCCCgcGHKs8eg54?X9jtFAR[%d=L0N_F,nb0P['>_r-]l*jq$7]J\`D-mi9\[PE)p@_=7<'f$4+[O6G!%5]X9cY\Bu=llBUH"#itWt49O[\'"i`_horVorP;SX,B(6_<11P]kjgU@h5%[%$W#f.Mg)#9?Ob.14?IA(G.kmD!`&^.$\m1PDK-cnm=?Y;HLapQP#SDQ:(-%\S)GK*9U\rWW#*q0FonFb,RM)Zbf"HL$n[R.^Sg&,rhWbl=pl$4o.9[cdWZNfR1%"G1FO0MiLPkrd1#R%7n$BlT"DnIY#-J,m+=-1W@dfYK/!^0,&er.U']CBO"lji&JVFr9BFo,=I#FR8#_q44;^k>9(-QAo:/e`Tqm*qgV.:VZD]SjVPnENY:D470=Dt3g=++4ID[Ea&QF^_recDH\&Bu.#,u64OAM&4q\qM?lou-p8f553?IlF\D#akaC>@I-A)3@LpnmnBCdOLH^NK/]q#5>4u!G;4^BG?.kcjo*SKEn6O,qo$8';A%7?aP&:n9th"ZB;\6jMg72nt/jFoRL,0++6M16]2,3A;(oS4.4CRiYaFu,8B2_/]b&`_;?;5nkqBXr\3=Zj34a*?g1g]=ti6CcG[30(E,394MWo`DHVT@OtVBIUq.ES%OJ$L`qIWR3)4]A4"co_Ro]@mDNTj.\cT5hs`k5+M-lj1H*Ee'%cZT..mL^/SkQ[BP%BrN_Om/c=OJd',PF+)$Cn47O1)2]dK(Ni%^HOPA3S-h+mtOS?fq*m]Q!g&DN.g(3cJp$9V['Q)7ZY=A4,Hl36I[XCo4:!hr,%%k`/:cZRJZM\7bf`:(H]lnaF2!AjC*IhE&G62KC1nPendstream +endobj +10 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1169 +>> +stream +Gat=)gN)=4%"7kOi%_jCBo/!$3IT%]]=k#H\bIm4N'ob_PphonS@W'7OKrCW'1^N3`qIJ5!"M9P0SBFud!t3n^src-^P!.r=KNa*8IVY(pp-@e2%00",UEJN(kn2c,`.[$rQlO/Zein[69#+m?r^W#MP49?8mSs5KYW5]l`(Sti)\D9>#bh;]=QOSf?.4?V6]4sXM2U;ZcdiZc[-W52W?D_NVR&GP)$aQ?W5l2oK;+!.83[MWBo&M0Uo5'2A_bHG#>V@.,25S)l/T@YAeM9')>k/mNXm/u43qJAiMHbWd9DOVs^kdjulbH`H]gJpEh,!6b4_gc[fNO32:N,i1[B_>+Dp_h+h62$!E:`X*F'V&K2o<@6C1C[XCY6"!@hO@/37bGj_AIJI*EB2^;_p_;Xm$B4EHF&q6P1NR@f$HF^ohc+P0F\(ERrW+0I6"U#M&-/CD-F>`n8J?qWsk(fN@#mfOCI!<6nB)1akT2mpI[.If?W97N\loGX58EKVJ<8@W25!_L=""A"X%.#EIB9,=<@O>NZS3-:CLo:l=]E(mPg>f%If[".g^%-jE*'>05H5%"e;J+m:&FC=flil9Ge-Yi536Xf5eL[LF?F?U*`:>H5I4^-6l)Eu$V!=?Ug9a9g@E8.AfF",u?LshMU;0E6EAT^""NX*PFa9V(;c9Ih,QM&2I&nRnHP="DLR(2#3&e%t'\$S"Z0AJlHGYq&of2@umC3hXVMYk.MJS)\Y(nKTtLKtZ!]Ek\-Zf$"&r;s(%#'iU_8/Foq[cqqPmg.h169@&<;/c(@qgH2/,kKCQ5=Ig)E7"ge4mZ>[Vc-*SBJ+[^JZHYGhRr/oVj+C5^-GdO;CBL2Q;UL6mhsB6&,T*`=?Jm>*r=a`dQ#NY7dU[H9g=D<@keA(.XhY]GEM_V`GlubcD(n1G,Sr'@Yag2pusAc9Ao6m2:T-\Gk0DKEh$N\P*j_Ihsp;d7P^FP8I06a+26#k!j@6&tUToMN9i^,$9Q#C95cFq+-endstream +endobj +xref +0 11 +0000000000 65535 f +0000000061 00000 n +0000000102 00000 n +0000000209 00000 n +0000000321 00000 n +0000000524 00000 n +0000000728 00000 n +0000000796 00000 n +0000001102 00000 n +0000001167 00000 n +0000004095 00000 n +trailer +<< +/ID +[<8697b2018a3d689f70795c513a06ba7d><8697b2018a3d689f70795c513a06ba7d>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 7 0 R +/Root 6 0 R +/Size 11 +>> +startxref +5356 +%%EOF diff --git a/docs/_pdf_build/build_graphvis_pivot_pdf.py b/docs/_pdf_build/build_graphvis_pivot_pdf.py new file mode 100644 index 0000000..7e21a32 --- /dev/null +++ b/docs/_pdf_build/build_graphvis_pivot_pdf.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +"""Build tmp/GRAFANA_vs_others_graphvis_pivot.pdf decision memo.""" + +from __future__ import annotations + +from pathlib import Path + +from reportlab.lib import colors +from reportlab.lib.enums import TA_LEFT +from reportlab.lib.pagesizes import A4 +from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet +from reportlab.lib.units import cm +from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle + +ROOT = Path(__file__).resolve().parents[2] +OUT_PDF = ROOT / "tmp" / "GRAFANA_vs_others_graphvis_pivot.pdf" +SOURCE_MD = ROOT / "tmp" / "GRAFANA_vs_others_graphvis_pivot.md" + + +def p(text: str, style) -> Paragraph: + return Paragraph(text.replace("\n", "
"), style) + + +def add_table(story, headers: list[str], rows: list[list[str]], col_widths) -> None: + data = [headers] + rows + t = Table(data, colWidths=col_widths) + t.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1565C0")), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.white), + ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"), + ("FONTSIZE", (0, 0), (-1, -1), 9), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("GRID", (0, 0), (-1, -1), 0.5, colors.lightgrey), + ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#F5F5F5")]), + ("LEFTPADDING", (0, 0), (-1, -1), 6), + ("RIGHTPADDING", (0, 0), (-1, -1), 6), + ("TOPPADDING", (0, 0), (-1, -1), 4), + ("BOTTOMPADDING", (0, 0), (-1, -1), 4), + ] + ) + ) + story.append(t) + + +def build_pdf() -> None: + styles = getSampleStyleSheet() + title = ParagraphStyle( + "DocTitle", + parent=styles["Title"], + fontSize=18, + spaceAfter=10, + textColor=colors.HexColor("#1565C0"), + ) + h1 = ParagraphStyle("H1", parent=styles["Heading1"], fontSize=14, spaceBefore=12, spaceAfter=6) + body = ParagraphStyle("Body", parent=styles["Normal"], fontSize=10, leading=14, alignment=TA_LEFT) + small = ParagraphStyle("Small", parent=body, fontSize=9, textColor=colors.grey) + bullet = ParagraphStyle("Bullet", parent=body, leftIndent=14, bulletIndent=6) + + story: list = [] + story.append(p("Android Cast — Graph Visualization Pivot", title)) + story.append(p("Scope: LAMP + nginx backend, no data duplication pipeline, role-based analytics.", small)) + story.append(Spacer(1, 0.25 * cm)) + + add_table( + story, + ["Field", "Value"], + [ + ["Date", "2026-06-02"], + ["Context", "apps.f0xx.org + BE Alpine (nginx + php-fpm + MariaDB)"], + ["Constraint", "No cron ETL/data copy/pipeline complexity"], + ["Output", "Decision memo: Grafana vs custom PHP dashboards"], + ["Markdown source", str(SOURCE_MD.relative_to(ROOT))], + ], + [4.2 * cm, 12.2 * cm], + ) + + story.append(Spacer(1, 0.35 * cm)) + story.append(p("1. Executive summary", h1)) + story.append( + p( + "• Grafana is viable only if it can query MariaDB directly (or via native SQL datasource) with strict RBAC mapping and SSO/session bridging.
" + "• If direct DB + auth integration becomes complex, custom PHP dashboards are lower risk and fit your stack with zero infra additions.
" + "• Recommended path: hybrid — build canonical SQL views + metrics contract once; start with custom PHP pages, optionally add Grafana later reusing same SQL.", + bullet, + ) + ) + + story.append(Spacer(1, 0.35 * cm)) + story.append(p("2. Option comparison", h1)) + add_table( + story, + ["Option", "Pros", "Cons / Risks", "Stack impact"], + [ + [ + "Grafana (direct DB)", + "Fast charting, polished visuals, alerting, flexible panels", + "Session/RBAC integration effort; slug scoping must be enforced in queries; additional service to operate", + "Adds Grafana service only (no ETL required)", + ], + [ + "Custom PHP + nginx", + "Native app auth/session reuse; direct BE logic access; deterministic tenant filtering", + "More frontend/dev work for visual polish; chart UX must be implemented", + "No new runtime dependencies", + ], + [ + "Hybrid (recommended)", + "Ship fast with PHP now, preserve Grafana option later; one metrics SQL contract", + "Needs discipline in shared metrics layer", + "Minimal immediate changes", + ], + ], + [3.1 * cm, 4.4 * cm, 5.0 * cm, 3.9 * cm], + ) + + story.append(Spacer(1, 0.35 * cm)) + story.append(p("3. Role-based graph pack (suggested)", h1)) + add_table( + story, + ["Role", "Core dashboards", "Example charts"], + [ + [ + "User", + "Own slug, own devices/sessions", + "DAU devices, avg cast duration, avg recv sessions/day, success rate, app version split", + ], + [ + "Slug admin", + "All slug analytics + health", + "Active/passive users, bandwidth send/recv 1d/7d, install source pie (Play/OTA/custom), NTP source usage and correction savings", + ], + [ + "Platform admin", + "Cross-slug global + reliability", + "Crashes per slug/device/user, trend by app version/SDK, links to ticket IDs/issues, top regressions by fingerprint", + ], + ], + [2.7 * cm, 5.1 * cm, 8.6 * cm], + ) + + story.append(Spacer(1, 0.35 * cm)) + story.append(p("4. Effort and cost estimate (engineering)", h1)) + add_table( + story, + ["Track", "Initial delivery", "Hardening", "Total estimate"], + [ + ["Grafana direct DB", "4-7 dev days", "5-8 dev days", "9-15 dev days"], + ["Custom PHP dashboards", "5-9 dev days", "3-6 dev days", "8-15 dev days"], + ["Hybrid phase-1 PHP then Grafana", "6-10 dev days", "optional +4-7 days", "10-17 dev days"], + ], + [4.3 * cm, 3.8 * cm, 3.8 * cm, 4.5 * cm], + ) + story.append( + p( + "Assumptions: existing MariaDB schema with crash/ticket/session tables, role data available; excludes major schema migration.", + small, + ) + ) + + story.append(Spacer(1, 0.35 * cm)) + story.append(p("5. Recommendation under your constraints", h1)) + story.append( + p( + "• Do not introduce ETL/duplication. Keep one source of truth: MariaDB + live SQL views.
" + "• Implement a metrics SQL layer now (views/materialized semantics via SQL only, no copied store).
" + "• Build custom PHP dashboard pages first for guaranteed auth/session + slug scoping correctness.
" + "• Re-evaluate Grafana after metrics contract stabilizes; adopt only if direct DB + RBAC mapping is clean.", + bullet, + ) + ) + + story.append(Spacer(1, 0.35 * cm)) + story.append(p("6. Proposed next implementation slice", h1)) + add_table( + story, + ["Step", "Deliverable", "ETA"], + [ + ["1", "Define metrics dictionary + SQL views for user/slug/admin scopes", "1-2 days"], + ["2", "Add heartbeat metric: time source in use + ntp correction savings counters", "0.5-1 day"], + ["3", "Create PHP dashboards: user and slug-admin pages with 1d/7d selectors", "3-5 days"], + ["4", "Add admin reliability board + crash/ticket links", "2-3 days"], + ["5", "Optional Grafana POC over same SQL views", "2-3 days"], + ], + [1.2 * cm, 11.9 * cm, 3.3 * cm], + ) + + doc = SimpleDocTemplate( + str(OUT_PDF), + pagesize=A4, + leftMargin=1.8 * cm, + rightMargin=1.8 * cm, + topMargin=1.6 * cm, + bottomMargin=1.6 * cm, + title="Grafana vs Others GraphVis Pivot", + author="Android Cast project", + ) + doc.build(story) + print(f"Wrote {OUT_PDF}") + + +if __name__ == "__main__": + build_pdf() diff --git a/scripts/build-graphvis-pdf.sh b/scripts/build-graphvis-pdf.sh new file mode 100644 index 0000000..8daceca --- /dev/null +++ b/scripts/build-graphvis-pdf.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" + +python3 "$ROOT_DIR/docs/_pdf_build/build_graphvis_pivot_pdf.py" +echo "Generated: $ROOT_DIR/tmp/GRAFANA_vs_others_graphvis_pivot.pdf" diff --git a/third-party/opus b/third-party/opus new file mode 160000 index 0000000..22244de --- /dev/null +++ b/third-party/opus @@ -0,0 +1 @@ +Subproject commit 22244de5a79bd1d6d623c32e72bf1954b56235be diff --git a/third-party/speex b/third-party/speex new file mode 160000 index 0000000..5dceaaf --- /dev/null +++ b/third-party/speex @@ -0,0 +1 @@ +Subproject commit 5dceaaf3e23ee7fd17c80cb5f02a838fd6c18e01