querysub 0.394.0 → 0.395.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursorrules +8 -0
- package/package.json +1 -1
- package/src/-a-archives/archivesJSONT.ts +71 -8
- package/src/0-path-value-core/pathValueCore.ts +20 -1
- package/src/5-diagnostics/GenericFormat.tsx +1 -1
- package/src/deployManager/components/MachinesListPage.tsx +1 -2
- package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +12 -3
- package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +8 -3
- package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +24 -9
- package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +0 -1
- package/src/diagnostics/logs/IndexedLogs/FindProgressTracker.ts +21 -5
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +10 -4
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +95 -124
- package/src/diagnostics/logs/IndexedLogs/RenderSearchStats.tsx +127 -0
- package/src/diagnostics/logs/IndexedLogs/bufferSearchFindMatcher.ts +3 -0
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +1 -1
- package/src/diagnostics/logs/TimeRangeSelector.tsx +11 -2
- package/src/diagnostics/logs/errorNotifications2/ErrorNotificationPage.tsx +1 -4
- package/src/diagnostics/logs/errorNotifications2/errorNotifications.ts +1 -1
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePage.tsx +946 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleMatching.ts +49 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleSearch.tsx +553 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +125 -90
- package/src/diagnostics/managementPages.tsx +17 -1
- package/src/functional/{limitProcessing.ts → throttleProcessing.ts} +1 -1
- package/src/library-components/StartEllipsis.tsx +13 -0
- package/src/misc.ts +4 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/test.wat +0 -106
- package/src/diagnostics/logs/lifeCycleAnalysis/test.wat.d.ts +0 -2
- package/src/diagnostics/logs/lifeCycleAnalysis/testHoist.ts +0 -5
|
@@ -1,94 +1,129 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
//todonext
|
|
19
|
-
// Reorganize our lifecycles ideas
|
|
20
|
-
// - Scans over past logs, and only finished, never pending
|
|
21
|
-
// - Probably... realtime creating cache, BUT, if we add new lifecycles, we then only add them on demand. So when we are in the debugging mindset, and adding and changing them, it's fast (only as needed), but when we come back later when we aren't in the middle of debugging, it'll be super fast so we can fix the bug quickly and move on.
|
|
22
|
-
// - OR... we can do pending dynamically, but we don't cache it? Hmm... then what's the point? Hmm...
|
|
23
|
-
// - MAYBE, no caching, but we restrict the time by a lot? HMM...
|
|
24
|
-
// - I mean, we have to implement it without caching anyways to start, so we can just do that, and then cache it later if we see the need...
|
|
25
|
-
// - WELL, we need SOME kind of caching... Maybe... we DO use a strict time range, and then... if we've scanned a pending file in the past, we can cache that, because we know we've got all of it's values. Hmm...
|
|
26
|
-
// - Use lifecycles to debug rejections. To a point, until it is likely we are out of sync, then we should write the sync verification code.
|
|
27
|
-
// - Maybe it isn't necessarily rejections, but definitely if we're running stuff locally and remotely it breaks. Could it be the case that we can't run the path value server both locally and remotely? And if so, we really should prevent this in some way.
|
|
28
|
-
// - Annoyingly enough, we're actually going to have to add a case where we can scan the local logs as well as the remote logs to debug running it locally...
|
|
29
|
-
// - ALTHOUGH, It does fail just if we have the local running server, But we're on the remote site. So we might be able to just debug it from that life cycle, from the remote life cycles.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// todonext;
|
|
33
|
-
// Hmm... so... should we index it, so we can search it? HMM... I think we might want to?
|
|
34
|
-
// - Although the searches might get a bit complicated...
|
|
35
|
-
// - I think we need to limit lifecycle lengths? Hmm... as otherwise we need a lot in memory at once?
|
|
36
|
-
// - We could always do it based on size, so if we have too many logs the max time length is less
|
|
37
|
-
// 7) Decide how we're going to store it, and setup the controller
|
|
38
|
-
// 8) Get the AI to set up some basic UI to manage it.
|
|
39
|
-
// - For now, we'll run the phases one after the other. Controlled by the caller. Caching is going to come much later.
|
|
40
|
-
// - The first phase I think we can run when we receive the logs directly. This will make progress nice. And then the second phase will run automatically in the finish function. We'll have to add additional progress for that, which shouldn't be too difficult.
|
|
41
|
-
// - Having the finish function fork off into an async is probably fine, although we will have to expose the cancellation promise to the finish function?
|
|
42
|
-
// - Or do we even need cancellation? Are we ever going to even keep this code? So if we have a server precaching the values, we still need the pending values to be parsed somewhere. Hmm... That's annoying. Are we just not going to use fast archives for this?
|
|
43
|
-
// - MAYBE We can have the Fast Archives support external caching, so it'll give us the name of the file, and we'll tell it if it's cached or not. Actually, it can give us the name of all the files, and we'll tell it if we have a cached result for it. And if we do, we'll load the cache result, and it won't do any of the parsing of that file.
|
|
44
|
-
// 9) Stress test it, by automating something to load a page repeatedly (which should cause a lot of path logs). We should try at least 10K page refreshes, and nothing should break...
|
|
45
|
-
// - If my estimates are correct, and a page load of a large book results in ~5K logs, that will use ~2.5MB of uncompressed space, (~100KB compressed). Which... is acceptable. 10K page loads is 1GB, so a million would cost 1 USD/month to store. AND that's if we keep storing all audits in the logs, which we don't really have to do...
|
|
46
|
-
// - Processing it, on the other hand, will be kind of slow. Looks like we decompress about ten MB a second. But... there's not much we can do about that... unless... we searched on the compressed data directly? Hmm... or at least optimized based on it.
|
|
47
|
-
// 10) Add log file overview page, so we can keep an eye on the size of logs
|
|
48
|
-
// 10.1) Start storing the log count in the log file names
|
|
49
|
-
// - WHICH, requires breaking all of our old logs, but... that's not too big of deal
|
|
50
|
-
// - ONLY for the non-pending logs
|
|
51
|
-
// 11) Pull requests our lz4-wasm code. It's purely AI generated, but it might be of some interest.
|
|
52
|
-
// - As in, supporting streaming and decoding from a stream
|
|
53
|
-
// - REMEMBER to delete all of our test files and test code, and also really clean up the pull request before we make it.
|
|
54
|
-
/*
|
|
55
|
-
Two phases, and second phase has limitted, as some of our life cycles might explode quadratically.
|
|
56
|
-
*/
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// logs => life cycle related => life cycle group by key => life cycle list, with each one being expandable
|
|
60
|
-
|
|
61
|
-
// Searching in previous state
|
|
62
|
-
// Using variables from logs?
|
|
63
|
-
// Using variables from lifecycle?
|
|
64
|
-
// The FORM we match will be pretty much always the same (not depending on variables). If we absolutely need to we can do some late filtering in the second phase, but I don't think we'll need to. However the life cycle it becomes a part of, that will depend on the state.
|
|
65
|
-
// OH! The start lifecycle entry will create a lifecycle, THEN we match. The later matches might not be as restrictive as the start life cycle. SOO...
|
|
66
|
-
// start = [path, clientId]
|
|
67
|
-
// watch = [path]
|
|
68
|
-
// AND SO, This is what we can do to kind of expand our paths. So really, it's more of a debug thing, where the start is something that provides a key for what we're debugging. And then after it, we'll have different lines which can match whichever data we want.
|
|
69
|
-
// - The only restriction is the other lines have to happen after the start and before the end.
|
|
70
|
-
// - The end will be the first end matched as well
|
|
71
|
-
// - AND Optionally, you can make it so that the start ends the previous one
|
|
72
|
-
// - And you can also just not provide an end. There's no reason the life cycle needs to end.
|
|
73
|
-
// - I think technically if you add a start and it keeps triggering and you don't tell it that it should end the previous one, then there'll be a lot of overlapping and some of the life cycles will get quite large. But we can handle that by throttling and lazy evaluation, so it shouldn't actually be an issue.
|
|
74
|
-
// todonext;
|
|
75
|
-
// // Okay, so it's looking like the first thing we're going to do is match the start, and then once we find the start, if we want to expand that life cycle, then we do a second phase search.
|
|
76
|
-
// todonext;
|
|
77
|
-
// // HMM... So actually what kind of variables do we need? I guess if we want to map between IDs. But then can't we just Match some line That gives us the mapping. And then, presumably, that'll be early on in the life cycle....
|
|
78
|
-
// todonext;
|
|
79
|
-
// // OH! Okay, so we want to allow some logs in the life cycle to match before the start? But that's all. They just match before the start. And I think we have to define how long they can match before the start. Obviously, we can't search forever. And then they're just normal lines after that. They can set life cycle variables, and we can access life cycle variables.
|
|
80
|
-
// todonext;
|
|
81
|
-
// // Yes, so... The variables are going to be a lot simple. It's just the lifecycle will have variables associated with it, which lines in the life cycle can set.
|
|
82
|
-
// todonext;
|
|
83
|
-
// // Let's not have any regex parsing either. Lifecycles entries can just say, I care about these variables and we expect them to be a part of the log object, and then we add them to the lifecycle.
|
|
84
|
-
// // - Which is really more for UI organization, as we can say these are important. And prevents the bug of some random log having to have happen to having a thread ID, but we have a thread ID and we mean the client thread ID, etc., etc. We just make it explicit per line, and then we don't run into that bug ever.
|
|
85
|
-
|
|
86
|
-
// TODO: Eventually, if we need it, we can have entries find other life cycles, either matching many, the latest, etc, and take variables from them PURELY FOR DISPLAY PURPOSES. But right at this moment, that's not needed.
|
|
87
|
-
|
|
88
|
-
type LifeCycle = {
|
|
89
|
-
|
|
1
|
+
import { lazy } from "socket-function/src/caching";
|
|
2
|
+
import { nestArchives } from "../../../-a-archives/archives";
|
|
3
|
+
import { getArchivesBackblaze } from "../../../-a-archives/archivesBackBlaze";
|
|
4
|
+
import { archiveJSONT } from "../../../-a-archives/archivesJSONT";
|
|
5
|
+
import { getDomain } from "../../../config";
|
|
6
|
+
import { runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
7
|
+
import { timeInMinute } from "socket-function/src/misc";
|
|
8
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
9
|
+
import { assertIsManagementUser } from "../../managementPages";
|
|
10
|
+
import { getSyncedController } from "../../../library-components/SyncedController";
|
|
11
|
+
|
|
12
|
+
export type LifeCycle = {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
entries: LifeCycleEntry[];
|
|
90
16
|
};
|
|
91
17
|
|
|
92
|
-
type LifeCycleEntry = {
|
|
18
|
+
export type LifeCycleEntry = {
|
|
19
|
+
description?: string;
|
|
20
|
+
|
|
21
|
+
sourceType: "log" | "error" | "info" | "warning";
|
|
22
|
+
|
|
23
|
+
matchPattern: string;
|
|
24
|
+
|
|
25
|
+
// There could be multiple starts and multiple ends. Ideally the UI should move anything set to the start or the end to the start and end of the entries list.
|
|
26
|
+
isStart?: boolean;
|
|
27
|
+
isEnd?: boolean;
|
|
93
28
|
|
|
29
|
+
groupByKeys: {
|
|
30
|
+
ourKey: string;
|
|
31
|
+
// Defaults to the same key, which is usually the case
|
|
32
|
+
startKey?: string;
|
|
33
|
+
}[];
|
|
34
|
+
|
|
35
|
+
// All of the special variables, which are marked. The title defaults to the key, but can be overriden for the purposes of adding notes.
|
|
36
|
+
variables: {
|
|
37
|
+
[key: string]: {
|
|
38
|
+
title?: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
94
41
|
};
|
|
42
|
+
export function getVariables(entry: LifeCycleEntry): { key: string, title?: string }[] {
|
|
43
|
+
let variables: { key: string, title?: string }[] = [];
|
|
44
|
+
for (let groupByKey of entry.groupByKeys) {
|
|
45
|
+
variables.push({ key: groupByKey.ourKey, title: "Group By Key" });
|
|
46
|
+
}
|
|
47
|
+
for (let [key, value] of Object.entries(entry.variables)) {
|
|
48
|
+
variables.push({ key, title: value.title });
|
|
49
|
+
}
|
|
50
|
+
return variables;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const lifeCycles = archiveJSONT<LifeCycle>(() => nestArchives("logs/life-cycles/", getArchivesBackblaze(getDomain())));
|
|
54
|
+
let lifeCyclesCache: LifeCycle[] = [];
|
|
55
|
+
|
|
56
|
+
let ensureWatching = lazy(async () => {
|
|
57
|
+
await runInfinitePollCallAtStart(timeInMinute * 5, updateNow);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
async function updateNow() {
|
|
61
|
+
lifeCyclesCache = await lifeCycles.values();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function getLifeCyclesEntries(): Promise<LifeCycle[]> {
|
|
65
|
+
await ensureWatching();
|
|
66
|
+
return lifeCyclesCache;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class LifeCyclesService {
|
|
70
|
+
public async getLifeCycles() {
|
|
71
|
+
return await getLifeCyclesEntries();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public async setLifeCycle(lifeCycle: LifeCycle) {
|
|
75
|
+
let promise = lifeCycles.set(lifeCycle.id, lifeCycle);
|
|
76
|
+
let prevIndex = lifeCyclesCache.findIndex(e => e.id === lifeCycle.id);
|
|
77
|
+
if (prevIndex !== -1) {
|
|
78
|
+
lifeCyclesCache[prevIndex] = lifeCycle;
|
|
79
|
+
} else {
|
|
80
|
+
lifeCyclesCache.push(lifeCycle);
|
|
81
|
+
}
|
|
82
|
+
void promise.finally(() => {
|
|
83
|
+
void updateNow();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public async deleteLifeCycle(id: string) {
|
|
88
|
+
let promise = lifeCycles.delete(id);
|
|
89
|
+
let prevIndex = lifeCyclesCache.findIndex(e => e.id === id);
|
|
90
|
+
if (prevIndex !== -1) {
|
|
91
|
+
lifeCyclesCache.splice(prevIndex, 1);
|
|
92
|
+
}
|
|
93
|
+
void promise.finally(() => {
|
|
94
|
+
void updateNow();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const LifeCyclesServiceBase = SocketFunction.register(
|
|
100
|
+
"LifeCyclesService-019cb585-0381-730b-9a25-a2c647e503ee",
|
|
101
|
+
new LifeCyclesService(),
|
|
102
|
+
() => ({
|
|
103
|
+
getLifeCycles: {},
|
|
104
|
+
setLifeCycle: {},
|
|
105
|
+
deleteLifeCycle: {},
|
|
106
|
+
}),
|
|
107
|
+
() => ({
|
|
108
|
+
hooks: [assertIsManagementUser],
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
export const LifeCyclesController = getSyncedController(LifeCyclesServiceBase, {
|
|
113
|
+
reads: {
|
|
114
|
+
getLifeCycles: ["lifeCycles"],
|
|
115
|
+
},
|
|
116
|
+
writes: {
|
|
117
|
+
setLifeCycle: ["lifeCycles"],
|
|
118
|
+
deleteLifeCycle: ["lifeCycles"],
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
@@ -27,6 +27,7 @@ import { addComponentUI } from "../5-diagnostics/qreactDebug";
|
|
|
27
27
|
import { addComponentButton } from "../5-diagnostics/qreactDebug";
|
|
28
28
|
import { closeAllModals } from "../5-diagnostics/Modal";
|
|
29
29
|
import { delay } from "socket-function/src/batching";
|
|
30
|
+
import { currentViewParam, selectedServiceIdParam } from "../deployManager/urlParams";
|
|
30
31
|
|
|
31
32
|
export const managementPageURL = new URLParam("managementpage", "");
|
|
32
33
|
export const showingManagementURL = new URLParam("showingmanagement", false);
|
|
@@ -73,6 +74,7 @@ export async function registerManagementPages2(config: {
|
|
|
73
74
|
let inputPages: typeof config.pages = [];
|
|
74
75
|
|
|
75
76
|
|
|
77
|
+
// IMPORTANT! The controller name has to be the name of the controller in the file. No human would just randomly put some junk there, but the AI keeps putting random junk there and being surprised when they get an error because that controller doesn't actually exist in that file.
|
|
76
78
|
inputPages.push({
|
|
77
79
|
title: "Machines",
|
|
78
80
|
componentName: "MachinesPage",
|
|
@@ -89,6 +91,12 @@ export async function registerManagementPages2(config: {
|
|
|
89
91
|
componentName: "LogViewer3",
|
|
90
92
|
getModule: () => import("./logs/IndexedLogs/LogViewer3"),
|
|
91
93
|
});
|
|
94
|
+
inputPages.push({
|
|
95
|
+
title: "Life Cycles",
|
|
96
|
+
componentName: "LifeCyclePage",
|
|
97
|
+
controllerName: "",
|
|
98
|
+
getModule: () => import("./logs/lifeCycleAnalysis/LifeCyclePage"),
|
|
99
|
+
});
|
|
92
100
|
inputPages.push({
|
|
93
101
|
title: "Error Notifications",
|
|
94
102
|
componentName: "ErrorNotificationPage",
|
|
@@ -330,8 +338,16 @@ class ManagementRoot extends qreact.Component {
|
|
|
330
338
|
}
|
|
331
339
|
`}
|
|
332
340
|
</style>
|
|
333
|
-
<div class={css.fillWidth.hbox(
|
|
341
|
+
<div class={css.fillWidth.hbox(20, 4).wrap.hsl(245, 25, 80).pad2(20, 4).pointerEvents("all")}>
|
|
334
342
|
<ErrorWarning />
|
|
343
|
+
<ATag values={[
|
|
344
|
+
managementPageURL.getOverride("MachinesPage"),
|
|
345
|
+
currentViewParam.getOverride("services"),
|
|
346
|
+
]}>Deploy Machines</ATag>
|
|
347
|
+
<ATag values={[
|
|
348
|
+
managementPageURL.getOverride("MachinesPage"),
|
|
349
|
+
currentViewParam.getOverride("deploy"),
|
|
350
|
+
]}>Deploy Application</ATag>
|
|
335
351
|
{pages.map(page =>
|
|
336
352
|
<ATag values={[{ param: managementPageURL, value: page.componentName }]}>{page.title}</ATag>
|
|
337
353
|
)}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { measureFnc } from "socket-function/src/profiling/measure";
|
|
2
2
|
import { MaybePromise } from "socket-function/src/types";
|
|
3
3
|
|
|
4
|
-
export class
|
|
4
|
+
export class ThrottleGroup {
|
|
5
5
|
/** We have processing sections. In each section, if we exceed the current maximum wait, then any new processing will be told that it needs to wait. And then at the end of the section we wait wait time. This can be zero, which is fine, and we'll should wait enough time for networking, etc to run. */
|
|
6
6
|
constructor(public config: {
|
|
7
7
|
maxTimePerBeforeWait: number;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { qreact } from "../4-dom/qreact";
|
|
2
|
+
import { css } from "typesafecss";
|
|
3
|
+
|
|
4
|
+
export class StartEllipsis extends qreact.Component<{
|
|
5
|
+
maxWidth: number;
|
|
6
|
+
}> {
|
|
7
|
+
render() {
|
|
8
|
+
return <div className={css.relative.maxWidth(this.props.maxWidth).overflowHidden.whiteSpace("nowrap").direction("rtl")}>
|
|
9
|
+
<span className={css.relative.direction("ltr")}>{this.props.children}</span>
|
|
10
|
+
<span className={css.absolute.left(`calc((100% - ${this.props.maxWidth}px) * -10000)`).top(0).height("100%").width(30).background("linear-gradient(to right, white, transparent)").pointerEvents("none")}></span>
|
|
11
|
+
</div>;
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/misc.ts
CHANGED
|
@@ -190,4 +190,8 @@ export function streamToIteratable<T>(reader: {
|
|
|
190
190
|
const AsyncFunction = (async () => { }).constructor;
|
|
191
191
|
export function isAsyncFunction(func: unknown): boolean {
|
|
192
192
|
return func instanceof AsyncFunction;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function maybeUndefined<T>(value: T): T | undefined {
|
|
196
|
+
return value;
|
|
193
197
|
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
(module
|
|
2
|
-
;; Import memory from JavaScript (with memory64 support for >4GB).
|
|
3
|
-
;; Callers use allocateBuffer(exports, size) from supportWat.ts to
|
|
4
|
-
;; request scratch regions; memory will be grown automatically as needed.
|
|
5
|
-
(import "env" "memory" (memory i64 1))
|
|
6
|
-
|
|
7
|
-
;; Export the heap base so the allocator knows where static data ends.
|
|
8
|
-
;; With no static data section here, the heap starts at byte 0.
|
|
9
|
-
(global (export "__heap_base") i32 (i32.const 0))
|
|
10
|
-
|
|
11
|
-
;; Add function: adds two i32 integers
|
|
12
|
-
(func $add (param $a i32) (param $b i32) (result i32)
|
|
13
|
-
local.get $a
|
|
14
|
-
local.get $b
|
|
15
|
-
i32.add
|
|
16
|
-
)
|
|
17
|
-
(export "add" (func $add))
|
|
18
|
-
|
|
19
|
-
;; Multiply function: multiplies two i32 integers
|
|
20
|
-
(func $multiply (param $a i32) (param $b i32) (result i32)
|
|
21
|
-
local.get $a
|
|
22
|
-
local.get $b
|
|
23
|
-
i32.mul
|
|
24
|
-
)
|
|
25
|
-
(export "multiply" (func $multiply))
|
|
26
|
-
|
|
27
|
-
;; stddev(offset: i32, count: i32) -> f64
|
|
28
|
-
;;
|
|
29
|
-
;; Computes the population standard deviation of `count` f64 values
|
|
30
|
-
;; stored contiguously in memory starting at byte `offset`.
|
|
31
|
-
;; Each value occupies 8 bytes (little-endian IEEE 754 double).
|
|
32
|
-
;; Returns 0.0 when count == 0.
|
|
33
|
-
(func $stddev (param $offset i32) (param $count i32) (result f64)
|
|
34
|
-
(local $i i32)
|
|
35
|
-
(local $sum f64)
|
|
36
|
-
(local $mean f64)
|
|
37
|
-
(local $diff f64)
|
|
38
|
-
(local $variance_sum f64)
|
|
39
|
-
|
|
40
|
-
;; Guard: return 0 if count == 0
|
|
41
|
-
(if (i32.eqz (local.get $count))
|
|
42
|
-
(then (return (f64.const 0)))
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
;; Pass 1: compute sum
|
|
46
|
-
(local.set $i (i32.const 0))
|
|
47
|
-
(local.set $sum (f64.const 0))
|
|
48
|
-
(block $break1
|
|
49
|
-
(loop $loop1
|
|
50
|
-
(br_if $break1 (i32.ge_u (local.get $i) (local.get $count)))
|
|
51
|
-
(local.set $sum
|
|
52
|
-
(f64.add
|
|
53
|
-
(local.get $sum)
|
|
54
|
-
(f64.load
|
|
55
|
-
(i32.add
|
|
56
|
-
(local.get $offset)
|
|
57
|
-
(i32.mul (local.get $i) (i32.const 8))
|
|
58
|
-
)
|
|
59
|
-
)
|
|
60
|
-
)
|
|
61
|
-
)
|
|
62
|
-
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
63
|
-
(br $loop1)
|
|
64
|
-
)
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
;; mean = sum / count
|
|
68
|
-
(local.set $mean
|
|
69
|
-
(f64.div (local.get $sum) (f64.convert_i32_u (local.get $count)))
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
;; Pass 2: compute sum of squared deviations
|
|
73
|
-
(local.set $i (i32.const 0))
|
|
74
|
-
(local.set $variance_sum (f64.const 0))
|
|
75
|
-
(block $break2
|
|
76
|
-
(loop $loop2
|
|
77
|
-
(br_if $break2 (i32.ge_u (local.get $i) (local.get $count)))
|
|
78
|
-
(local.set $diff
|
|
79
|
-
(f64.sub
|
|
80
|
-
(f64.load
|
|
81
|
-
(i32.add
|
|
82
|
-
(local.get $offset)
|
|
83
|
-
(i32.mul (local.get $i) (i32.const 8))
|
|
84
|
-
)
|
|
85
|
-
)
|
|
86
|
-
(local.get $mean)
|
|
87
|
-
)
|
|
88
|
-
)
|
|
89
|
-
(local.set $variance_sum
|
|
90
|
-
(f64.add
|
|
91
|
-
(local.get $variance_sum)
|
|
92
|
-
(f64.mul (local.get $diff) (local.get $diff))
|
|
93
|
-
)
|
|
94
|
-
)
|
|
95
|
-
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
96
|
-
(br $loop2)
|
|
97
|
-
)
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
;; return sqrt(variance_sum / count)
|
|
101
|
-
(f64.sqrt
|
|
102
|
-
(f64.div (local.get $variance_sum) (f64.convert_i32_u (local.get $count)))
|
|
103
|
-
)
|
|
104
|
-
)
|
|
105
|
-
(export "stddev" (func $stddev))
|
|
106
|
-
)
|