Skip to content

Commit 11ef7b8

Browse files
committed
Merge branch 'develop' into wip/db/14142-ydoc-websocket
2 parents c148fb4 + 96ac95d commit 11ef7b8

File tree

35 files changed

+550
-216
lines changed

35 files changed

+550
-216
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
- [Launching another Enso process via `Process_Builder`][14753]
3030
- [Redshift schema support.][14735]
3131
- [`Process_Builder.set_env`.][14799]
32+
- [New `Profile` API for timing code execution.][14827]
3233

3334
[14522]: https://github.com/enso-org/enso/pull/14522
3435
[14476]: https://github.com/enso-org/enso/pull/14476
@@ -42,6 +43,7 @@
4243
[14753]: https://github.com/enso-org/enso/pull/14753
4344
[14735]: https://github.com/enso-org/enso/pull/14735
4445
[14799]: https://github.com/enso-org/enso/pull/14799
46+
[14827]: https://github.com/enso-org/enso/pull/14827
4547

4648
#### Enso Language & Runtime
4749

app/common/src/services/Backend/types.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,32 @@ export const KSUID = newtypeConstructor<KSUID>()
1010
export type OrganizationId = Newtype<`organization-${string}`, 'OrganizationId'>
1111
export const OrganizationId = newtypeConstructor<OrganizationId>()
1212
/** Whether a given {@link string} is an {@link OrganizationId}. */
13-
export function isOrganizationId(id: unknown): id is OrganizationId {
14-
return typeof id === 'string' && id.startsWith('organization-')
13+
export function isOrganizationId(id: string): id is OrganizationId {
14+
return id.startsWith('organization-')
1515
}
1616

1717
/** Unique identifier for a user in an organization. */
1818
export type UserId = Newtype<string, 'UserId'>
1919
export const UserId = newtypeConstructor<UserId>()
2020
/** Whether a given {@link string} is an {@link UserId}. */
21-
export function isUserId(id: unknown): id is UserId {
22-
return typeof id === 'string' && id.startsWith('user-')
21+
export function isUserId(id: string): id is UserId {
22+
return id.startsWith('user-')
2323
}
2424

2525
/** Unique identifier for a user group. */
2626
export type UserGroupId = Newtype<`usergroup-${string}`, 'UserGroupId'>
2727
export const UserGroupId = newtypeConstructor<UserGroupId>()
2828
/** Whether a given {@link string} is an {@link UserGroupId}. */
29-
export function isUserGroupId(id: unknown): id is UserGroupId {
30-
return typeof id === 'string' && id.startsWith('usergroup-')
29+
export function isUserGroupId(id: string): id is UserGroupId {
30+
return id.startsWith('usergroup-')
3131
}
3232

3333
/** Unique identifier for a directory. */
3434
export type DirectoryId = Newtype<`directory-${string}`, 'DirectoryId'>
3535
export const DirectoryId = newtypeConstructor<DirectoryId>()
3636
/** Whether a given {@link unknown} is an {@link DirectoryId}. */
37-
export function isDirectoryId(id: unknown): id is DirectoryId {
38-
return typeof id === 'string' && id.startsWith('directory-')
37+
export function isDirectoryId(id: string): id is DirectoryId {
38+
return id.startsWith('directory-')
3939
}
4040

4141
/**
@@ -63,8 +63,8 @@ export const ErrorAssetId = newtypeConstructor<ErrorAssetId>()
6363
export type ProjectId = Newtype<string, 'ProjectId'>
6464
export const ProjectId = newtypeConstructor<ProjectId>()
6565
/** Whether a given {@link unknown} is an {@link ProjectId}. */
66-
export function isProjectId(id: unknown): id is ProjectId {
67-
return typeof id === 'string' && id.startsWith('project-')
66+
export function isProjectId(id: string): id is ProjectId {
67+
return id.startsWith('project-')
6868
}
6969

7070
/** Unique identifier for an uploaded file. */

app/common/src/services/LocalBackend.ts

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -779,9 +779,24 @@ export class LocalBackend extends backend.Backend {
779779
}
780780

781781
/** Resolve path to asset. In case of LocalBackend, this is just the filesystem path. */
782-
override resolveEnsoPath(path: backend.EnsoPath): Promise<backend.AnyAsset> {
782+
override async resolveEnsoPath(path: backend.EnsoPath): Promise<backend.AnyAsset> {
783783
const { directoryPath } = getDirectoryAndName(Path(path as string))
784-
return this.findAsset(directoryPath, 'ensoPath', path)
784+
const directoryContents = await this.listDirectory({
785+
parentId: newDirectoryId(directoryPath),
786+
filterBy: null,
787+
labels: null,
788+
recentProjects: false,
789+
rootPath: this.rootPath(),
790+
sortExpression: null,
791+
sortDirection: null,
792+
from: null,
793+
pageSize: null,
794+
})
795+
const entry = directoryContents.assets.find((asset) => asset.ensoPath === path)
796+
if (entry == null) {
797+
throw new backend.AssetDoesNotExistError()
798+
}
799+
return entry
785800
}
786801

787802
/** Resolve the data of a project asset relative to the project root directory. */
@@ -1129,33 +1144,6 @@ export class LocalBackend extends backend.Backend {
11291144
override getMapboxToken() {
11301145
return this.invalidOperation()
11311146
}
1132-
1133-
/** Find asset details using directory listing. */
1134-
private async findAsset<Key extends keyof backend.AnyAsset>(
1135-
directory: Path,
1136-
key: Key,
1137-
value: backend.AnyAsset[Key],
1138-
) {
1139-
const directoryContents = await this.listDirectory({
1140-
parentId: newDirectoryId(directory),
1141-
filterBy: null,
1142-
labels: null,
1143-
recentProjects: false,
1144-
rootPath: this.rootPath(),
1145-
sortExpression: null,
1146-
sortDirection: null,
1147-
from: null,
1148-
pageSize: null,
1149-
})
1150-
const entry = directoryContents.assets.find((content) => content[key] === value)
1151-
if (entry == null) {
1152-
if (backend.isDirectoryId(value)) {
1153-
throw new backend.DirectoryDoesNotExistError()
1154-
}
1155-
throw new backend.AssetDoesNotExistError()
1156-
}
1157-
return entry as never
1158-
}
11591147
}
11601148

11611149
markRaw(LocalBackend.prototype)
Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
/** @file ID encoding and decoding that is specific to cloud backend. */
2-
import { DirectoryId, UserGroupId, UserId, type AssetId, type OrganizationId } from '../Backend.js'
3-
import { TEAMS_DIRECTORY_ID, USERS_DIRECTORY_ID } from '../Backend/remoteBackendPaths.js'
4-
5-
/** Whether the given directory is a special directory that cannot be written to. */
6-
export function isSpecialReadonlyDirectoryId(id: AssetId) {
7-
return id === USERS_DIRECTORY_ID || id === TEAMS_DIRECTORY_ID
8-
}
2+
import { DirectoryId, UserGroupId, UserId, type OrganizationId } from '../Backend.js'
93

104
/**
115
* Extract the ID from the given user group ID.
@@ -51,52 +45,7 @@ export function userIdToDirectoryId(id: UserId): DirectoryId {
5145
return DirectoryId(`directory-${extractIdFromUserId(id)}`)
5246
}
5347

54-
/**
55-
* Convert a directory ID to a user ID.
56-
* @param id - The directory ID.
57-
* @returns The user ID.
58-
*/
59-
export function directoryIdToUserId(id: DirectoryId): UserId {
60-
return UserId(`user-${extractIdFromDirectoryId(id)}`)
61-
}
62-
6348
/** Convert organization ID to a directory ID. */
6449
export function organizationIdToDirectoryId(id: OrganizationId): DirectoryId {
6550
return DirectoryId(`directory-${extractIdFromOrganizationId(id)}`)
6651
}
67-
68-
/**
69-
* Convert a directory ID to a user group ID.
70-
* @param id - The directory ID.
71-
* @returns The user group ID.
72-
*/
73-
export function directoryIdToUserGroupId(id: DirectoryId): UserGroupId {
74-
return UserGroupId(`usergroup-${extractIdFromDirectoryId(id)}`)
75-
}
76-
77-
/**
78-
* Whether the given string is a valid organization ID.
79-
* @param id - The string to check.
80-
* @returns Whether the string is a valid organization ID.
81-
*/
82-
export function isOrganizationId(id: string): id is OrganizationId {
83-
return id.startsWith('organization-')
84-
}
85-
86-
/**
87-
* Whether the given string is a valid user ID.
88-
* @param id - The string to check.
89-
* @returns Whether the string is a valid user ID.
90-
*/
91-
export function isUserId(id: string): id is UserId {
92-
return id.startsWith('user-')
93-
}
94-
95-
/**
96-
* Whether the given string is a valid user group ID.
97-
* @param id - The string to check.
98-
* @returns Whether the string is a valid user group ID.
99-
*/
100-
export function idIsUserGroupId(id: string): id is UserGroupId {
101-
return id.startsWith('usergroup-')
102-
}

app/gui/src/dashboard/pages/dashboard/Drive/DriveBar/DriveBarNavigation.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export function DriveBarNavigation() {
151151
const canNavigateUp = parentId >= 0
152152

153153
const navigateToDirectory = useEventCallback((id: React.Key) => {
154-
if (isDirectoryId(id)) {
154+
if (typeof id === 'string' && isDirectoryId(id)) {
155155
setDriveLocation(id, category.id)
156156
}
157157
})
@@ -163,11 +163,9 @@ export function DriveBarNavigation() {
163163
return
164164
}
165165

166-
if (!isDirectoryId(id)) {
167-
return
166+
if (typeof id === 'string' && isDirectoryId(id)) {
167+
await moveAssetsMutation([[...selectedIds], id])
168168
}
169-
170-
await moveAssetsMutation([[...selectedIds], id])
171169
})
172170

173171
const navigateToParent = useEventCallback(() => {

distribution/lib/Standard/Base/0.0.0-dev/docs/api/Logging.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
- Info
77
- Severe
88
- Warning
9+
- type Profile
10+
- time_execution level:(Standard.Base.Logging.Log_Level|Standard.Base.Nothing.Nothing) ~label:Standard.Base.Data.Text.Text ~function:Standard.Base.Any.Any suffix_callback:Standard.Base.Any.Any= module:Standard.Base.Any.Any= -> Standard.Base.Any.Any
911
- type Progress
1012
- advance self amount:Standard.Base.Data.Numbers.Integer= -> Standard.Base.Logging.Progress
1113
- close self -> Standard.Base.Any.Any
1214
- log self detail:Standard.Base.Data.Text.Text -> Standard.Base.Logging.Progress
1315
- run label:Standard.Base.Data.Text.Text up_to:Standard.Base.Data.Numbers.Integer action:(Standard.Base.Logging.Progress -> Standard.Base.Any.Any) step:Standard.Base.Data.Numbers.Integer= handle_name:Standard.Base.Data.Text.Text= -> Standard.Base.Any.Any
1416
- to_text self -> Standard.Base.Any.Any
1517
- Standard.Base.Any.Any.log_message self ~message:Standard.Base.Data.Text.Text level:Standard.Base.Logging.Log_Level= -> Standard.Base.Any.Any
18+
- Standard.Base.Logging.Log_Level.from that:Standard.Base.Data.Text.Text -> Standard.Base.Logging.Log_Level

distribution/lib/Standard/Base/0.0.0-dev/src/Logging.enso

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import project.Any.Any
22
import project.Data.Numbers.Integer
33
import project.Data.Text.Text
4+
import project.Data.Time.Duration.Duration
45
import project.Error.Error
56
import project.Errors.Illegal_Argument.Illegal_Argument
67
import project.Internal.Meta_Helpers
78
import project.Meta
9+
import project.Nothing.Nothing
810
import project.Runtime.Managed_Resource.Managed_Resource
911
import project.Runtime.Ref.Ref
1012
from project.Data.Boolean import False, True
13+
from project.Data.Text.Extensions import all
1114

1215
polyglot java import org.enso.base.ProgressHandle
1316
polyglot java import org.slf4j.LoggerFactory
@@ -133,6 +136,48 @@ type Log_Level
133136
## Severe level log message.
134137
Severe
135138

139+
Log_Level.from (that:Text) =
140+
case that.to_case ..Lower of
141+
"finest" -> ..Finest
142+
"trace" -> ..Finest
143+
"fine" -> ..Fine
144+
"debug" -> ..Fine
145+
"info" -> ..Info
146+
"warning" -> ..Warning
147+
_ -> ..Severe
148+
149+
## Utility type for profiling
150+
151+
Should be controlled by an environment variable set to the log level:
152+
- `ENSO_PROFILE_VIZZES` - for visualizations.
153+
- `ENSO_PROFILE_SQL` - for SQL queries.
154+
- `ENSO_PROFILE_TABLE` - for table function calls.
155+
type Profile
156+
## ---
157+
advanced: true
158+
icon: time
159+
---
160+
Time the evaluation of a function, log the duration and return the result.
161+
162+
## Arguments
163+
- `level`: Log level to log the duration at. If `Nothing`, the duration is not logged.
164+
- `label`: Label to log with the duration.
165+
- `function`: Function to execute.
166+
- `suffix_callback`: Optional callback to generate a suffix for the log message based
167+
on the result of the function. The callback is only called if `level` is not `Nothing`.
168+
- `module`: Module to log the message to. Defaults to `Profile`.
169+
time_execution level:(Log_Level | Nothing) ~label:Text ~function suffix_callback=Nothing module=Profile =
170+
if Nothing == level then @Tail_Call function else
171+
pair = Duration.time_execution function
172+
173+
## If the function threw an error, we just return the error
174+
Error.return_if_error pair.second
175+
176+
duration = pair.first
177+
suffix = if suffix_callback == Nothing then "" else suffix_callback pair.second
178+
module.log_message ("Execution time for " + label + ": " + duration.to_text + suffix) level
179+
pair.second
180+
136181
private log_impl logger ~message level args =
137182
builder = case level of
138183
Log_Level.Finest -> logger.atTrace

distribution/lib/Standard/Database/0.0.0-dev/src/Internal/JDBC_Connection.enso

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ from Standard.Base import all
22
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
33
import Standard.Base.Errors.Illegal_State.Illegal_State
44
import Standard.Base.Errors.Unimplemented.Unimplemented
5+
import Standard.Base.Logging.Log_Level
6+
import Standard.Base.Logging.Profile
57
import Standard.Base.Runtime.Context
68
import Standard.Base.Runtime.Managed_Resource.Managed_Resource
9+
import Standard.Base.Runtime.State
710
from Standard.Base.Enso_Cloud.Enso_Secret import as_hideable_value
811

912
from Standard.Table import Table, Value_Type
@@ -61,7 +64,7 @@ type JDBC_Connection
6164
Runs the provided callback only if no thread is currently inside a
6265
`synchronized` critical section (including the current thread).
6366
run_maintenance_action_if_possible : (Nothing -> Any) -> Nothing
64-
run_maintenance_action_if_possible self callback =
67+
run_maintenance_action_if_possible self callback = Context.Output.with_enabled <|
6568
self.operation_synchronizer.runMaintenanceActionIfPossible callback
6669

6770
## ---
@@ -256,23 +259,23 @@ private _set_statement_values stmt:PreparedStatement statement_setter:Statement_
256259
A helper that logs performed SQL queries/statements run-time to a profiling
257260
file, if an environment variable is set.
258261
private _profile_sql_if_enabled (jdbc_connection : JDBC_Connection) (~query_text : Text) ~action (skip_log:Boolean=False) =
259-
path = if skip_log then "" else
260-
Environment.get "ENSO_SQL_PROFILING_PATH" . if_nothing Environment.get "ENSO_SQL_LOG_PATH" . if_nothing ""
261-
if path.is_empty then action else
262-
duration_and_result = Duration.time_execution action
263-
result = duration_and_result.second
264-
## If the action returns a dataflow error, `time_execution` returns the error, no duration.
265-
We could work around this. But we don't want to - the failing queries likely did not run anyway,
266-
so their time info would most likely be an outlier. For current purposes it makes sense to ignore such cases.
267-
Error.return_if_error result
268-
269-
padded_query = query_text.to_display_text.pad length=80
270-
millis = duration_and_result.first.total_milliseconds
271-
millis_text = (millis.to_text.pad length=5 with_pad=' ' at=..Left) + ' ms'
272-
273-
# E.g. [SQLite] SELECT * FROM "test" ... --> 12 ms
262+
make_label =
274263
db_id = jdbc_connection.with_metadata .getDatabaseProductName
275-
log_line = "[" + db_id + "] " + padded_query + " --> " + millis_text + '\n'
264+
"[" + db_id + "] " + query_text.to_display_text
265+
266+
result = Profile.time_execution _profile_level label=make_label module=JDBC_Connection <|
267+
action
268+
269+
## If the action returns a dataflow error, `time_execution` returns the error, no duration.
270+
We could work around this. But we don't want to - the failing queries likely did not run anyway,
271+
so their time info would most likely be an outlier. For current purposes it makes sense to ignore such cases.
272+
Error.return_if_error result
273+
274+
path = if skip_log then "" else Environment.get "ENSO_SQL_LOG_PATH" . if_nothing ""
275+
if path.is_empty then result else
276+
# E.g. [Time] [SQLite] SELECT * FROM "test"
277+
padded_query = make_label.pad 100
278+
log_line = "[" + Date_Time.now.to_text + "] " + padded_query + '\n'
276279
Context.Output.with_enabled <|
277280
log_line.write path on_existing_file=Existing_File_Behavior.Append
278281

@@ -285,3 +288,11 @@ private _profile_sql_if_enabled (jdbc_connection : JDBC_Connection) (~query_text
285288
this Connection concurrently.
286289
private _synchronized (jdbc_connection : JDBC_Connection) ~action =
287290
jdbc_connection.operation_synchronizer.runSynchronizedAction _->action
291+
292+
private _profile_level = State.get Table if_missing=_profile_level_from_env
293+
294+
private _profile_level_from_env =
295+
env_val = Environment.get "ENSO_PROFILE_SQL"
296+
if env_val == Nothing then Nothing else
297+
level = Log_Level.from env_val
298+
if level.is_error then Nothing else level

0 commit comments

Comments
 (0)