add display resolutions to activity updates, update docs (#1323)

This commit is contained in:
Mike Sawka 2024-11-19 19:41:53 -08:00 committed by GitHub
parent 958a5ece10
commit 24807cfd34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 93 additions and 83 deletions

View File

@ -47,11 +47,20 @@ When telemetry is active, we collect the following data. It is stored in the `te
| ActiveMinutes | The number of minutes that the user has actively used Waveterm on a given day. This requires the terminal window to be in focus while the user is actively interacting with it. | | ActiveMinutes | The number of minutes that the user has actively used Waveterm on a given day. This requires the terminal window to be in focus while the user is actively interacting with it. |
| FgMinutes | The number of minutes that Waveterm has been in the foreground on a given day. This requires the terminal window to be in focus regardless of user interaction. | | FgMinutes | The number of minutes that Waveterm has been in the foreground on a given day. This requires the terminal window to be in focus regardless of user interaction. |
| OpenMinutes | The number of minutes that Waveterm has been open on a given day. This only requires that the terminal is open, even if the window is out of focus. | | OpenMinutes | The number of minutes that Waveterm has been open on a given day. This only requires that the terminal is open, even if the window is out of focus. |
| NumBlocks | The number of existing blocks open on a given day |
| NumTabs | The number of existing tabs open on a given day. | | NumTabs | The number of existing tabs open on a given day. |
| NewTab | The number of new tabs created on a given day |
| NumWindows | The number of existing windows open on a give day. |
| NewTab | The number of new tabs opened on a given day. | | NewTab | The number of new tabs opened on a given day. |
| NumStartup | The number of times waveterm has been started on a given day. | | NumStartup | The number of times waveterm has been started on a given day. |
| NumShutdown | The number of times waveterm has been shut down on a given day. | | NumShutdown | The number of times waveterm has been shut down on a given day. |
| SetTabTheme | The number of times the tab theme is changed from the context menu |
| NumMagnify | The number of times any block is magnified |
| NumSSHConn | The number of distinct SSH connections that have been made to distinct hosts |
| NumWSLConns | The number of distinct WSL connections that have been made to distinct distros |
| Renderers | The number of new block views of each type are open on a given day. | | Renderers | The number of new block views of each type are open on a given day. |
| WshCmds | The number of wsh commands of each type run on a given day |
| Conn | The number of successful remote connections made (and errors) on a given day |
## Associated Data ## Associated Data
@ -67,6 +76,7 @@ In addition to the telemetry data collected, the following is also reported. It
| ClientArch | This includes the user's operating system (e.g. linux or darwin) and architecture (e.g. x86_64 or arm64). It does not include data for any Connections at this time. | | ClientArch | This includes the user's operating system (e.g. linux or darwin) and architecture (e.g. x86_64 or arm64). It does not include data for any Connections at this time. |
| BuildTime | This serves as a more accurate version number that keeps track of when we built the version. It has no bearing on when that version was installed by you. | | BuildTime | This serves as a more accurate version number that keeps track of when we built the version. It has no bearing on when that version was installed by you. |
| OSRelease | This lists the version of the operating system the user has installed. | | OSRelease | This lists the version of the operating system the user has installed. |
| Displays | Display resolutions (added in v0.9.3 to help us understand what screen resolutions to optimize for) |
## Telemetry Metadata ## Telemetry Metadata

View File

@ -1,6 +1,7 @@
// Copyright 2024, Command Line Inc. // Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
import { RpcApi } from "@/app/store/wshclientapi";
import * as electron from "electron"; import * as electron from "electron";
import { FastAverageColor } from "fast-average-color"; import { FastAverageColor } from "fast-average-color";
import fs from "fs"; import fs from "fs";
@ -14,7 +15,6 @@ import winston from "winston";
import * as services from "../frontend/app/store/services"; import * as services from "../frontend/app/store/services";
import { initElectronWshrpc, shutdownWshrpc } from "../frontend/app/store/wshrpcutil"; import { initElectronWshrpc, shutdownWshrpc } from "../frontend/app/store/wshrpcutil";
import { getWebServerEndpoint } from "../frontend/util/endpoints"; import { getWebServerEndpoint } from "../frontend/util/endpoints";
import { fetch } from "../frontend/util/fetchutil";
import * as keyutil from "../frontend/util/keyutil"; import * as keyutil from "../frontend/util/keyutil";
import { fireAndForget } from "../frontend/util/util"; import { fireAndForget } from "../frontend/util/util";
import { AuthKey, configureAuthKeyRequestInjection } from "./authkey"; import { AuthKey, configureAuthKeyRequestInjection } from "./authkey";
@ -518,16 +518,39 @@ electron.ipcMain.on("contextmenu-show", (event, menuDefArr?: ElectronContextMenu
event.returnValue = true; event.returnValue = true;
}); });
// we try to set the primary display as index [0]
function getActivityDisplays(): ActivityDisplayType[] {
const displays = electron.screen.getAllDisplays();
const primaryDisplay = electron.screen.getPrimaryDisplay();
const rtn: ActivityDisplayType[] = [];
for (const display of displays) {
const adt = {
width: display.size.width,
height: display.size.height,
dpr: display.scaleFactor,
internal: display.internal,
};
if (display.id === primaryDisplay?.id) {
rtn.unshift(adt);
} else {
rtn.push(adt);
}
}
return rtn;
}
async function logActiveState() { async function logActiveState() {
const astate = getActivityState(); const astate = getActivityState();
const activeState = { fg: astate.wasInFg, active: astate.wasActive, open: true }; const activity: ActivityUpdate = { openminutes: 1 };
const url = new URL(getWebServerEndpoint() + "/wave/log-active-state"); if (astate.wasInFg) {
try { activity.fgminutes = 1;
const resp = await fetch(url, { method: "post", body: JSON.stringify(activeState) });
if (!resp.ok) {
console.log("error logging active state", resp.status, resp.statusText);
return;
} }
if (astate.wasActive) {
activity.activeminutes = 1;
}
activity.displays = getActivityDisplays();
try {
RpcApi.ActivityCommand(ElectronWshClient, activity, { noresponse: true });
} catch (e) { } catch (e) {
console.log("error logging active state", e); console.log("error logging active state", e);
} finally { } finally {

View File

@ -5,6 +5,14 @@
declare global { declare global {
// telemetry.ActivityDisplayType
type ActivityDisplayType = {
width: number;
height: number;
dpr: number;
internal?: boolean;
};
// telemetry.ActivityUpdate // telemetry.ActivityUpdate
type ActivityUpdate = { type ActivityUpdate = {
fgminutes?: number; fgminutes?: number;
@ -21,6 +29,7 @@ declare global {
shutdown?: number; shutdown?: number;
settabtheme?: number; settabtheme?: number;
buildtime?: string; buildtime?: string;
displays?: ActivityDisplayType[];
renderers?: {[key: string]: number}; renderers?: {[key: string]: number};
wshcmds?: {[key: string]: number}; wshcmds?: {[key: string]: number};
conn?: {[key: string]: number}; conn?: {[key: string]: number};

View File

@ -18,16 +18,11 @@ import (
const MaxTzNameLen = 50 const MaxTzNameLen = 50
// "terminal" should not be in this list type ActivityDisplayType struct {
var allowedRenderers = map[string]bool{ Width int `json:"width"`
"markdown": true, Height int `json:"height"`
"code": true, DPR float64 `json:"dpr"`
"openai": true, Internal bool `json:"internal,omitempty"`
"csv": true,
"image": true,
"pdf": true,
"media": true,
"mustache": true,
} }
type ActivityUpdate struct { type ActivityUpdate struct {
@ -45,6 +40,7 @@ type ActivityUpdate struct {
Shutdown int `json:"shutdown,omitempty"` Shutdown int `json:"shutdown,omitempty"`
SetTabTheme int `json:"settabtheme,omitempty"` SetTabTheme int `json:"settabtheme,omitempty"`
BuildTime string `json:"buildtime,omitempty"` BuildTime string `json:"buildtime,omitempty"`
Displays []ActivityDisplayType `json:"displays,omitempty"`
Renderers map[string]int `json:"renderers,omitempty"` Renderers map[string]int `json:"renderers,omitempty"`
WshCmds map[string]int `json:"wshcmds,omitempty"` WshCmds map[string]int `json:"wshcmds,omitempty"`
Conn map[string]int `json:"conn,omitempty"` Conn map[string]int `json:"conn,omitempty"`
@ -76,6 +72,7 @@ type TelemetryData struct {
NumStartup int `json:"numstartup,omitempty"` NumStartup int `json:"numstartup,omitempty"`
NumShutdown int `json:"numshutdown,omitempty"` NumShutdown int `json:"numshutdown,omitempty"`
SetTabTheme int `json:"settabtheme,omitempty"` SetTabTheme int `json:"settabtheme,omitempty"`
Displays []ActivityDisplayType `json:"displays,omitempty"`
Renderers map[string]int `json:"renderers,omitempty"` Renderers map[string]int `json:"renderers,omitempty"`
WshCmds map[string]int `json:"wshcmds,omitempty"` WshCmds map[string]int `json:"wshcmds,omitempty"`
Conn map[string]int `json:"conn,omitempty"` Conn map[string]int `json:"conn,omitempty"`
@ -180,6 +177,9 @@ func UpdateActivity(ctx context.Context, update ActivityUpdate) error {
tdata.Conn[key] += val tdata.Conn[key] += val
} }
} }
if len(update.Displays) > 0 {
tdata.Displays = update.Displays
}
query = `UPDATE db_activity query = `UPDATE db_activity
SET tdata = ?, SET tdata = ?,
clientversion = ?, clientversion = ?,

View File

@ -26,14 +26,11 @@ import (
"github.com/wavetermdev/waveterm/pkg/docsite" "github.com/wavetermdev/waveterm/pkg/docsite"
"github.com/wavetermdev/waveterm/pkg/filestore" "github.com/wavetermdev/waveterm/pkg/filestore"
"github.com/wavetermdev/waveterm/pkg/service" "github.com/wavetermdev/waveterm/pkg/service"
"github.com/wavetermdev/waveterm/pkg/telemetry"
"github.com/wavetermdev/waveterm/pkg/wavebase" "github.com/wavetermdev/waveterm/pkg/wavebase"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wshrpc" "github.com/wavetermdev/waveterm/pkg/wshrpc"
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshserver" "github.com/wavetermdev/waveterm/pkg/wshrpc/wshserver"
"github.com/wavetermdev/waveterm/pkg/wshutil" "github.com/wavetermdev/waveterm/pkg/wshutil"
"github.com/wavetermdev/waveterm/pkg/wstore"
) )
type WebFnType = func(http.ResponseWriter, *http.Request) type WebFnType = func(http.ResponseWriter, *http.Request)
@ -358,34 +355,6 @@ type ClientActiveState struct {
Open bool `json:"open"` Open bool `json:"open"`
} }
// params: fg, active, open
func handleLogActiveState(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body)
var activeState ClientActiveState
err := decoder.Decode(&activeState)
if err != nil {
WriteJsonError(w, fmt.Errorf("error decoding json: %v", err))
return
}
activity := telemetry.ActivityUpdate{}
if activeState.Fg {
activity.FgMinutes = 1
}
if activeState.Active {
activity.ActiveMinutes = 1
}
if activeState.Open {
activity.OpenMinutes = 1
}
activity.NumTabs, _ = wstore.DBGetCount[*waveobj.Tab](r.Context())
err = telemetry.UpdateActivity(r.Context(), activity)
if err != nil {
WriteJsonError(w, fmt.Errorf("error updating activity: %w", err))
return
}
WriteJsonSuccess(w, true)
}
func WebFnWrap(opts WebFnOpts, fn WebFnType) WebFnType { func WebFnWrap(opts WebFnOpts, fn WebFnType) WebFnType {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
defer func() { defer func() {
@ -450,7 +419,6 @@ func RunWebServer(listener net.Listener) {
gr.HandleFunc("/wave/stream-file", WebFnWrap(WebFnOpts{AllowCaching: true}, handleStreamFile)) gr.HandleFunc("/wave/stream-file", WebFnWrap(WebFnOpts{AllowCaching: true}, handleStreamFile))
gr.HandleFunc("/wave/file", WebFnWrap(WebFnOpts{AllowCaching: false}, handleWaveFile)) gr.HandleFunc("/wave/file", WebFnWrap(WebFnOpts{AllowCaching: false}, handleWaveFile))
gr.HandleFunc("/wave/service", WebFnWrap(WebFnOpts{JsonErrors: true}, handleService)) gr.HandleFunc("/wave/service", WebFnWrap(WebFnOpts{JsonErrors: true}, handleService))
gr.HandleFunc("/wave/log-active-state", WebFnWrap(WebFnOpts{JsonErrors: true}, handleLogActiveState))
gr.HandleFunc("/vdom/{uuid}/{path:.*}", WebFnWrap(WebFnOpts{AllowCaching: true}, handleVDom)) gr.HandleFunc("/vdom/{uuid}/{path:.*}", WebFnWrap(WebFnOpts{AllowCaching: true}, handleVDom))
gr.PathPrefix(docsitePrefix).Handler(http.StripPrefix(docsitePrefix, docsite.GetDocsiteHandler())) gr.PathPrefix(docsitePrefix).Handler(http.StripPrefix(docsitePrefix, docsite.GetDocsiteHandler()))
handler := http.TimeoutHandler(gr, HttpTimeoutDuration, "Timeout") handler := http.TimeoutHandler(gr, HttpTimeoutDuration, "Timeout")