@grnsft/if 0.1.2 → 0.1.3-beta

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.
Files changed (85) hide show
  1. package/CONTRIBUTING.md +13 -17
  2. package/README.md +36 -18
  3. package/build/config/config.d.ts +12 -0
  4. package/build/config/config.js +54 -0
  5. package/build/config/strings.d.ts +8 -3
  6. package/build/config/strings.js +10 -6
  7. package/build/index.js +15 -8
  8. package/build/lib/models-universe.d.ts +10 -2
  9. package/build/lib/models-universe.js +52 -34
  10. package/build/lib/observatory.d.ts +20 -0
  11. package/build/lib/observatory.js +31 -0
  12. package/build/lib/planet-aggregator.d.ts +6 -0
  13. package/build/lib/planet-aggregator.js +35 -0
  14. package/build/lib/supercomputer.d.ts +7 -0
  15. package/build/lib/supercomputer.js +45 -6
  16. package/build/models/index.d.ts +1 -0
  17. package/build/models/index.js +6 -0
  18. package/build/models/time-sync.d.ts +55 -0
  19. package/build/models/time-sync.js +235 -0
  20. package/build/types/helpers.d.ts +1 -0
  21. package/build/types/helpers.js +3 -0
  22. package/build/types/impl.d.ts +14 -9
  23. package/build/types/impl.js +3 -1
  24. package/build/types/model-interface.d.ts +14 -0
  25. package/build/types/model-interface.js +3 -0
  26. package/build/types/models-universe.d.ts +5 -6
  27. package/build/types/models-universe.js +1 -1
  28. package/build/types/planet-aggregator.d.ts +6 -0
  29. package/build/types/planet-aggregator.js +3 -0
  30. package/build/types/process-args.d.ts +7 -0
  31. package/build/types/process-args.js +3 -0
  32. package/build/types/supercomputer.d.ts +4 -0
  33. package/build/types/supercomputer.js +3 -0
  34. package/build/types/time-sync.d.ts +9 -0
  35. package/build/types/time-sync.js +3 -0
  36. package/build/types/units-dealer.d.ts +3 -0
  37. package/build/types/units-dealer.js +3 -0
  38. package/build/types/units.d.ts +11 -0
  39. package/build/types/units.js +37 -0
  40. package/build/util/args.js +58 -0
  41. package/build/util/errors.d.ts +6 -0
  42. package/build/util/errors.js +25 -0
  43. package/build/util/helpers.js +18 -0
  44. package/build/util/units-dealer.d.ts +10 -0
  45. package/build/util/units-dealer.js +32 -0
  46. package/build/util/validations.d.ts +4 -0
  47. package/build/util/validations.js +29 -1
  48. package/build/util/yaml.d.ts +1 -2
  49. package/build/util/yaml.js +1 -1
  50. package/coverage/clover.xml +111 -65
  51. package/coverage/coverage-final.json +7 -6
  52. package/coverage/lcov-report/config/config.ts.html +12 -3
  53. package/coverage/lcov-report/config/index.html +1 -1
  54. package/coverage/lcov-report/config/index.ts.html +3 -3
  55. package/coverage/lcov-report/config/strings.ts.html +2 -2
  56. package/coverage/lcov-report/index.html +9 -9
  57. package/coverage/lcov-report/lib/index.html +24 -9
  58. package/coverage/lcov-report/lib/models-universe.ts.html +1 -1
  59. package/coverage/lcov-report/lib/observatory.ts.html +7 -7
  60. package/coverage/lcov-report/lib/planet-aggregator.ts.html +253 -0
  61. package/coverage/lcov-report/lib/supercomputer.ts.html +211 -52
  62. package/coverage/lcov-report/util/args.ts.html +1 -1
  63. package/coverage/lcov-report/util/errors.ts.html +10 -7
  64. package/coverage/lcov-report/util/index.html +1 -1
  65. package/coverage/lcov-report/util/yaml.ts.html +1 -1
  66. package/coverage/lcov.info +184 -105
  67. package/examples/impls/case-studies/aggregation.yml +97 -0
  68. package/examples/impls/case-studies/ntt-data-on-premise.yaml +0 -42
  69. package/examples/impls/test/aggregation-test.yml +109 -0
  70. package/examples/impls/test/large-impl.yml +257303 -0
  71. package/examples/impls/test/time-sync.yml +75 -0
  72. package/examples/ompls/aggregation-test.yml +340 -0
  73. package/examples/ompls/test/time-sync.yml +255 -0
  74. package/examples/ompls/time-sync.yml +212 -0
  75. package/hack-banner.png +0 -0
  76. package/package.json +3 -1
  77. package/src/config/units.yaml +11 -24
  78. package/src/models/README.md +266 -0
  79. package/tsconfig.build.tsbuildinfo +1 -0
  80. package/build/types/azure-importer.d.ts +0 -29
  81. package/build/types/azure-importer.js +0 -3
  82. package/build/types/boavizta.d.ts +0 -7
  83. package/build/types/boavizta.js +0 -3
  84. package/build/types/common.d.ts +0 -7
  85. package/build/types/common.js +0 -9
@@ -0,0 +1,212 @@
1
+ name: nesting-demo
2
+ description: impl with 2 levels of nesting with non-uniform timing of observations
3
+ tags: null
4
+ initialize:
5
+ models:
6
+ - name: teads-curve
7
+ path: '@grnsft/if-unofficial-models'
8
+ model: TeadsCurveModel
9
+ - name: sci-e
10
+ path: '@grnsft/if-models'
11
+ model: SciEModel
12
+ - name: sci-m
13
+ path: '@grnsft/if-models'
14
+ model: SciMModel
15
+ - name: sci-o
16
+ path: '@grnsft/if-models'
17
+ model: SciOModel
18
+ - name: time-synchronization
19
+ path: builtin
20
+ model: TimeSyncModel
21
+ config:
22
+ start-time: 2023-12-12T00:00:00.000Z
23
+ end-time: 2023-12-12T00:01:00.000Z
24
+ interval: 5
25
+ graph:
26
+ children:
27
+ child:
28
+ pipeline:
29
+ - teads-curve
30
+ - sci-e
31
+ - sci-m
32
+ - sci-o
33
+ - time-synchronization
34
+ config:
35
+ teads-curve:
36
+ thermal-design-power: 65
37
+ sci-m:
38
+ total-embodied-emissions: 251000
39
+ time-reserved: 3600
40
+ expected-lifespan: 126144000
41
+ resources-reserved: 1
42
+ total-resources: 1
43
+ sci-o:
44
+ grid-carbon-intensity: 457
45
+ children:
46
+ child-1:
47
+ inputs:
48
+ - timestamp: 2023-12-12T00:00:00.000Z
49
+ duration: 10
50
+ cpu-util: 10
51
+ requests: 300
52
+ outputs:
53
+ - timestamp: '2023-12-12T00:00:00.000Z'
54
+ duration: 10
55
+ cpu-util: 10
56
+ requests: 30
57
+ thermal-design-power: 65
58
+ total-embodied-emissions: 251000
59
+ time-reserved: 3600
60
+ expected-lifespan: 126144000
61
+ resources-reserved: 1
62
+ total-resources: 1
63
+ grid-carbon-intensity: 457
64
+ energy-cpu: 0.000057777777777777776
65
+ energy: 0.000057777777777777776
66
+ embodied-carbon: 7.16324200913242
67
+ operational-carbon: 0.026404444444444442
68
+ carbon: 7.189646453576864
69
+ - timestamp: '2023-12-12T00:00:01.000Z'
70
+ duration: 10
71
+ cpu-util: 10
72
+ requests: 30
73
+ thermal-design-power: 65
74
+ total-embodied-emissions: 251000
75
+ time-reserved: 3600
76
+ expected-lifespan: 126144000
77
+ resources-reserved: 1
78
+ total-resources: 1
79
+ grid-carbon-intensity: 457
80
+ energy-cpu: 0.000057777777777777776
81
+ energy: 0.000057777777777777776
82
+ embodied-carbon: 7.16324200913242
83
+ operational-carbon: 0.026404444444444442
84
+ carbon: 7.189646453576864
85
+ - timestamp: '2023-12-12T00:00:02.000Z'
86
+ duration: 10
87
+ cpu-util: 10
88
+ requests: 30
89
+ thermal-design-power: 65
90
+ total-embodied-emissions: 251000
91
+ time-reserved: 3600
92
+ expected-lifespan: 126144000
93
+ resources-reserved: 1
94
+ total-resources: 1
95
+ grid-carbon-intensity: 457
96
+ energy-cpu: 0.000057777777777777776
97
+ energy: 0.000057777777777777776
98
+ embodied-carbon: 7.16324200913242
99
+ operational-carbon: 0.026404444444444442
100
+ carbon: 7.189646453576864
101
+ - timestamp: '2023-12-12T00:00:03.000Z'
102
+ duration: 10
103
+ cpu-util: 10
104
+ requests: 30
105
+ thermal-design-power: 65
106
+ total-embodied-emissions: 251000
107
+ time-reserved: 3600
108
+ expected-lifespan: 126144000
109
+ resources-reserved: 1
110
+ total-resources: 1
111
+ grid-carbon-intensity: 457
112
+ energy-cpu: 0.000057777777777777776
113
+ energy: 0.000057777777777777776
114
+ embodied-carbon: 7.16324200913242
115
+ operational-carbon: 0.026404444444444442
116
+ carbon: 7.189646453576864
117
+ - timestamp: '2023-12-12T00:00:04.000Z'
118
+ duration: 10
119
+ cpu-util: 10
120
+ requests: 30
121
+ thermal-design-power: 65
122
+ total-embodied-emissions: 251000
123
+ time-reserved: 3600
124
+ expected-lifespan: 126144000
125
+ resources-reserved: 1
126
+ total-resources: 1
127
+ grid-carbon-intensity: 457
128
+ energy-cpu: 0.000057777777777777776
129
+ energy: 0.000057777777777777776
130
+ embodied-carbon: 7.16324200913242
131
+ operational-carbon: 0.026404444444444442
132
+ carbon: 7.189646453576864
133
+ - timestamp: '2023-12-12T00:00:05.000Z'
134
+ duration: 10
135
+ cpu-util: 10
136
+ requests: 30
137
+ thermal-design-power: 65
138
+ total-embodied-emissions: 251000
139
+ time-reserved: 3600
140
+ expected-lifespan: 126144000
141
+ resources-reserved: 1
142
+ total-resources: 1
143
+ grid-carbon-intensity: 457
144
+ energy-cpu: 0.000057777777777777776
145
+ energy: 0.000057777777777777776
146
+ embodied-carbon: 7.16324200913242
147
+ operational-carbon: 0.026404444444444442
148
+ carbon: 7.189646453576864
149
+ - timestamp: '2023-12-12T00:00:06.000Z'
150
+ duration: 10
151
+ cpu-util: 10
152
+ requests: 30
153
+ thermal-design-power: 65
154
+ total-embodied-emissions: 251000
155
+ time-reserved: 3600
156
+ expected-lifespan: 126144000
157
+ resources-reserved: 1
158
+ total-resources: 1
159
+ grid-carbon-intensity: 457
160
+ energy-cpu: 0.000057777777777777776
161
+ energy: 0.000057777777777777776
162
+ embodied-carbon: 7.16324200913242
163
+ operational-carbon: 0.026404444444444442
164
+ carbon: 7.189646453576864
165
+ - timestamp: '2023-12-12T00:00:07.000Z'
166
+ duration: 10
167
+ cpu-util: 10
168
+ requests: 30
169
+ thermal-design-power: 65
170
+ total-embodied-emissions: 251000
171
+ time-reserved: 3600
172
+ expected-lifespan: 126144000
173
+ resources-reserved: 1
174
+ total-resources: 1
175
+ grid-carbon-intensity: 457
176
+ energy-cpu: 0.000057777777777777776
177
+ energy: 0.000057777777777777776
178
+ embodied-carbon: 7.16324200913242
179
+ operational-carbon: 0.026404444444444442
180
+ carbon: 7.189646453576864
181
+ - timestamp: '2023-12-12T00:00:08.000Z'
182
+ duration: 10
183
+ cpu-util: 10
184
+ requests: 30
185
+ thermal-design-power: 65
186
+ total-embodied-emissions: 251000
187
+ time-reserved: 3600
188
+ expected-lifespan: 126144000
189
+ resources-reserved: 1
190
+ total-resources: 1
191
+ grid-carbon-intensity: 457
192
+ energy-cpu: 0.000057777777777777776
193
+ energy: 0.000057777777777777776
194
+ embodied-carbon: 7.16324200913242
195
+ operational-carbon: 0.026404444444444442
196
+ carbon: 7.189646453576864
197
+ - timestamp: '2023-12-12T00:00:09.000Z'
198
+ duration: 10
199
+ cpu-util: 10
200
+ requests: 30
201
+ thermal-design-power: 65
202
+ total-embodied-emissions: 251000
203
+ time-reserved: 3600
204
+ expected-lifespan: 126144000
205
+ resources-reserved: 1
206
+ total-resources: 1
207
+ grid-carbon-intensity: 457
208
+ energy-cpu: 0.000057777777777777776
209
+ energy: 0.000057777777777777776
210
+ embodied-carbon: 7.16324200913242
211
+ operational-carbon: 0.026404444444444442
212
+ carbon: 7.189646453576864
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@grnsft/if",
3
3
  "description": "Impact Framework",
4
- "version": "v0.1.2",
4
+ "version": "v0.1.3-beta",
5
5
  "author": {
6
6
  "name": "Green Software Foundation",
7
7
  "email": "info@gsf.com"
@@ -14,6 +14,8 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "js-yaml": "^4.1.0",
17
+ "moment": "^2.29.4",
18
+ "moment-range": "^4.0.2",
17
19
  "ts-command-line-args": "^2.5.1",
18
20
  "typescript": "^5.1.6",
19
21
  "zod": "^3.22.4"
@@ -18,14 +18,18 @@ duration:
18
18
  description: refers to the duration of the input
19
19
  unit: seconds
20
20
  aggregation: sum
21
+ energy:
22
+ description: amount of energy utilised by the component
23
+ unit: kWh
24
+ aggregation: sum
21
25
  energy-cpu:
22
26
  description: Energy consumed by the CPU of the component
23
27
  unit: kWh
24
28
  aggregation: sum
25
29
  expected-lifespan:
26
- description: expected lifespan for some component
30
+ description: Total Expected Lifespan of the Component in Seconds
27
31
  unit: seconds
28
- aggregation: none
32
+ aggregation: sum
29
33
  energy-memory:
30
34
  description: Energy consumed by the Memory of the component
31
35
  unit: kWh
@@ -34,28 +38,16 @@ embodied-carbon:
34
38
  description: Embodied Emissions of the component
35
39
  unit: gCO2e
36
40
  aggregation: sum
37
- energy:
38
- description: amount of energy utilised by the component
39
- unit: kWh
40
- aggregation: sum
41
41
  energy-network:
42
42
  description: Energy consumed by the Network of the component
43
43
  unit: kWh
44
44
  aggregation: sum
45
- expected-lifespan:
46
- description: Total Expected Lifespan of the Component in Seconds
47
- unit: seconds
48
- aggregation: None
49
45
  functional-unit:
50
46
  description: the name of the functional unit in which the final SCI value should be expressed, e.g. requests, users
51
47
  unit: none
52
- aggregation: none
53
- functional-unit-duration:
54
- description: how many units of functional-unit-time the final SCI value should be expressed in.
55
- unit: functional-unit-time
56
- aggregation: none
48
+ aggregation: sum
57
49
  functional-unit-time:
58
- description: string describing the unit of time in which the final SCI calculation should be expressed
50
+ description: string describing the unit of time in which the final SCI calculation should be expressed, e.g. "1-min"
59
51
  unit: none
60
52
  aggregation: none
61
53
  gpu-util:
@@ -74,10 +66,6 @@ location:
74
66
  description: Geographic location of provider as string (for watt-time model it is provided as latitude and longitude, comma separated, in decimal degrees)
75
67
  unit: None (decimal degrees for watt-time model)
76
68
  aggregation: None
77
- embodied-carbon:
78
- description: Carbon emitted from component manufacture (returned from Boavizta)
79
- unit: gCO2eq
80
- aggregation: sum
81
69
  operational-carbon:
82
70
  description: Operational Emissions of the component
83
71
  unit: gCO2e
@@ -85,11 +73,11 @@ operational-carbon:
85
73
  physical-processor:
86
74
  description: Name of the physical processor
87
75
  unit: None
88
- aggregation: None
76
+ aggregation: none
89
77
  vendor:
90
78
  description: Name of the cloud service provider in the ccf model. Can be aws, gcp or azure
91
79
  unit: None
92
- aggregation: None
80
+ aggregation: none
93
81
  ram-alloc:
94
82
  description: refers to GB of memory allocated.
95
83
  unit: GB
@@ -113,7 +101,7 @@ total-embodied-emissions:
113
101
  timestamp:
114
102
  description: refers to the time of occurrence of the input
115
103
  unit: RFC3339
116
- aggregation: None
104
+ aggregation: none
117
105
  time-reserved:
118
106
  description: time reserved for a component
119
107
  unit: seconds
@@ -122,4 +110,3 @@ total-resources:
122
110
  description: total resources available
123
111
  unit: count
124
112
  aggregation: none
125
-
@@ -0,0 +1,266 @@
1
+ # Time sync
2
+
3
+ Time sync standardizes the start time, end time and temporal resolution of all output data across an entire graph.
4
+
5
+ ## Parameters
6
+
7
+ ### Model config
8
+ The following should be defined in the model initialization:
9
+
10
+ - `start-time`: global start time as ISO 8061 string
11
+ - `stop`: global end time as ISO 8061 string
12
+ - `interval`: temporal resolution in seconds
13
+
14
+ ### Inputs:
15
+
16
+ - `inputs`: an array of observations
17
+
18
+ ### Returns
19
+
20
+ - `inputs`: time-synchronized version of the graph
21
+
22
+
23
+ ## Concept
24
+
25
+ ### Overview
26
+
27
+ A manifest file for a graph might contain many nodes each representing some different part of an application's stack or even different applications running on different machines. It is therefore common to have time series data in each component that is not directly comparable to other components either because the temporal resolution of the data is different, they cover different periods, or there are gaps in some records (e.g. some apps might burst but then go dormant, while others run continuously). This makes post-hoc visualization, analysis and aggregation of data from groups of nodes difficult to achieve. To address this, we created a time synchronization model that takes in non-uniform times series and snaps them all to a regular timeline with uniform start time, end time and temporal resolution.
28
+
29
+ We do this by implementing the following logic:
30
+
31
+ - Shift readings to nearest whole seconds
32
+ - Upsample the time series to a base resolution (1s)
33
+ - Resample to desired resolution by batching 1s entries
34
+ - Extrapolate or trim to ensure all time series share global start and end dates
35
+
36
+ The next section explains each stage in more detail.
37
+
38
+ ### Details
39
+
40
+ #### Upsampling rules
41
+
42
+ A set of `inputs` is naturally a time series because all `observations` include a `timestamp` and a `duration`, measured in seconds.
43
+ For each `observation` in `inputs` we check whether the duration is greater than 1 second. If `duration` is greater than 1 second, we create N new `observation` objects, where N is equal to `duration`. This means we have an `observation` for every second between the initial timestamp and the end of the observation period. Each new object receives a timestamp incremented by one second.
44
+
45
+ This looks as follows:
46
+
47
+ ```
48
+ [{timestamp: 2023-12-12T00:00:00.000Z, duration: 5}]
49
+
50
+ # becomes
51
+ [
52
+ {timestamp: 2023-12-12T00:00:01.000Z, duration: 5}
53
+ {timestamp: 2023-12-12T00:00:02.000Z, duration: 5}
54
+ {timestamp: 2023-12-12T00:00:03.000Z, duration: 5}
55
+ {timestamp: 2023-12-12T00:00:04.000Z, duration: 5}
56
+ {timestamp: 2023-12-12T00:00:05.000Z, duration: 5}
57
+ ]
58
+ ```
59
+
60
+ Each `observation` actually includes many key-value pairs. The precise content of the `observation` is not known until runtime because it depends on which models have been included in the pipeline. Different values have to be treated differently when we upsample in time. The method we use to upsample depends on the `aggregation-method` defined for each key in `units.yml`.
61
+
62
+ If the right way to aggregate a value is to sum it, then the right way to upsample it is to divide by `duration`, effectively spreading the total out evenly across the new, higher resolution, `observations` so that the total across the same bucket of time is unchanged (i.e. if the total for some value is 10 when there is one entry with `duration = 10s`, then the total should still be 10 when there are 10 entries each witch `duration = 1s`).
63
+
64
+ On the other hand, if the right way to aggregate a value is to take its average over some time period, the value should be copied unchanged into the newly upsampled `observations`. This is appropriate for values that are proportional or percentages, such as `cpu-util`. Treating these values as constants means the average over the `duration` for an observation is identical whether you consider the initial `observation` or the upsampled set of N `observation`s.
65
+
66
+ Constants can simply be copied as-is, because they are constants. Examples might be the `grid-carbon-intensity` - this value does not change depending on how frequently you observe it.
67
+
68
+ Therefore, we apply this logic and the resulting flow looks as follows (the `aggregation-method` for `carbon` and `energy` is `sum`, `grid-carbon-intensity` is a constant and `cpu-util` is expressed as a percentage):
69
+
70
+ ```
71
+ [{timestamp: 2023-12-12T00:00:00.000Z, duration: 5, cpu-util: 12, carbon: 5, energy: 10, grid-carbon-intensity: 471}]
72
+
73
+ # becomes
74
+
75
+ [
76
+ {timestamp: 2023-12-12T00:00:00.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
77
+ {timestamp: 2023-12-12T00:00:01.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
78
+ {timestamp: 2023-12-12T00:00:02.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
79
+ {timestamp: 2023-12-12T00:00:03.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
80
+ {timestamp: 2023-12-12T00:00:04.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
81
+ {timestamp: 2023-12-12T00:00:05.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471}
82
+ ]
83
+ ```
84
+
85
+ The end result is that for each `observation`, we upsample the time series to yield 1 second resolution data between `timestamp` and `timestamp + duration`.
86
+
87
+ #### Gap-filling
88
+
89
+ Sometimes there might be discontinuities in the time series between one `observation` and another. For example we might have two `observations` in a set of `inputs` that have timestamps spaced 10 seconds apart, but the `duration` of the first `observation` is only 5 seconds. in this case, 5 seconds of data are unaccounted for and create a discontinuity in the time series.
90
+
91
+ To solve this problem, for all but the first `observation` in the `inputs` array, we grab the `timestamp` and `duration` from the previous `observation` and check that `timestamp[N] + duration[N] == timestamp[N+1]`. If this condition is not satisfied, we backfill the missing data with a "zero-observation" which is identical to the surrounding observations except any values whose `aggregation-method` is `sum` are set to zero. This is equivalent to assuming that when there is no data available, the app being monitored is switched off.
92
+
93
+ The end result of this gap-filling is that we have continuous 1 second resolution data that can be resampled to a new temporal resolution.
94
+
95
+ ```
96
+ [
97
+ {timestamp: 2023-12-12T00:00:00.000Z, duration: 5, cpu-util: 12, carbon: 5, energy: 10, grid-carbon-intensity: 471},
98
+ {timestamp: 2023-12-12T00:00:08.000Z, duration: 2, cpu-util: 12, carbon: 5, energy: 10, grid-carbon-intensity: 471}
99
+ ]
100
+
101
+ # There are 2 seconds of missing data between the end of timestamp[0] + duration, and timestamp[1]
102
+ # After expansion and infilling, the array becomes:
103
+
104
+ [
105
+ {timestamp: 2023-12-12T00:00:00.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
106
+ {timestamp: 2023-12-12T00:00:01.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
107
+ {timestamp: 2023-12-12T00:00:02.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
108
+ {timestamp: 2023-12-12T00:00:03.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
109
+ {timestamp: 2023-12-12T00:00:04.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
110
+ {timestamp: 2023-12-12T00:00:05.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
111
+ {timestamp: 2023-12-12T00:00:06.000Z, duration: 1, cpu-util: 0, carbon: 0, energy: 0, grid-carbon-intensity: 471},
112
+ {timestamp: 2023-12-12T00:00:07.000Z, duration: 1, cpu-util: 0, carbon: 0, energy: 0, grid-carbon-intensity: 471},
113
+ {timestamp: 2023-12-12T00:00:08.000Z, duration: 1, cpu-util: 12, carbon: 2.5, energy: 5, grid-carbon-intensity: 471},
114
+ {timestamp: 2023-12-12T00:00:09.000Z, duration: 1, cpu-util: 12, carbon: 2.5, energy: 5, grid-carbon-intensity: 471}
115
+ ]
116
+ ```
117
+
118
+ #### Trimming and padding
119
+
120
+ To ensure parity across all the components in a graph, we need to synchronize the start and end times for all time series. To do this, we pass the `time-sync` model plugin some global config: `startTime`, `endTime` and `interval`. The `startTime` is the timestamp where *all* input arrays across the entire graph should begin, and `endTime` is the timestamp where *all* input arrays across the entire graph should end. `interval` is the time resolution we ultimately want to resample to.
121
+
122
+ To synchronize the time series start and end we check the first element of `inputs` for each node in the graph and determine whether it is earlier, later or equal to the global start time. If it is equal then no action is required. If the `input` start time is earlier than the global start time, we simply discard entries from the front of the array until the start times are aligned. If the `input` start time is after the global start time, then we pad with our "zero-observation" object - one for every second separating the global start time from the `input` start time. The same process is repeated for the end time - we either trim away `input` data or pad it out with "zero-observation" objects.
123
+
124
+ For example, for `startTime = 2023-12-12T00:00:00.000Z` and `endTime = 2023-12-12T00:00:15.000Z`:
125
+
126
+ ```
127
+ [
128
+ {timestamp: 2023-12-12T00:00:05.000Z, duration: 5, cpu-util: 12, carbon: 5, energy: 10, grid-carbon-intensity: 471},
129
+ ]
130
+
131
+ # There are 5 seconds missing from the start and end. After padding, the array becomes:
132
+
133
+ [
134
+ {timestamp: 2023-12-12T00:00:00.000Z, duration: 1, cpu-util: 0, carbon: 0, energy: 0, grid-carbon-intensity: 471},
135
+ {timestamp: 2023-12-12T00:00:01.000Z, duration: 1, cpu-util: 0, carbon: 0, energy: 0, grid-carbon-intensity: 471},
136
+ {timestamp: 2023-12-12T00:00:02.000Z, duration: 1, cpu-util: 0, carbon: 0, energy: 0, grid-carbon-intensity: 471},
137
+ {timestamp: 2023-12-12T00:00:03.000Z, duration: 1, cpu-util: 0, carbon: 0, energy: 0, grid-carbon-intensity: 471},
138
+ {timestamp: 2023-12-12T00:00:04.000Z, duration: 1, cpu-util: 0, carbon: 0, energy: 0, grid-carbon-intensity: 471},
139
+ {timestamp: 2023-12-12T00:00:05.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
140
+ {timestamp: 2023-12-12T00:00:06.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
141
+ {timestamp: 2023-12-12T00:00:07.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
142
+ {timestamp: 2023-12-12T00:00:08.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
143
+ {timestamp: 2023-12-12T00:00:09.000Z, duration: 1, cpu-util: 12, carbon: 1, energy: 2, grid-carbon-intensity: 471},
144
+ {timestamp: 2023-12-12T00:00:10.000Z, duration: 1, cpu-util: 0, carbon: 0, energy: 0, grid-carbon-intensity: 471},
145
+ {timestamp: 2023-12-12T00:00:11.000Z, duration: 1, cpu-util: 0, carbon: 0, energy: 0, grid-carbon-intensity: 471},
146
+ {timestamp: 2023-12-12T00:00:12.000Z, duration: 1, cpu-util: 0, carbon: 0, energy: 0, grid-carbon-intensity: 471},
147
+ {timestamp: 2023-12-12T00:00:13.000Z, duration: 1, cpu-util: 0, carbon: 0, energy: 0, grid-carbon-intensity: 471},
148
+ {timestamp: 2023-12-12T00:00:14.000Z, duration: 1, cpu-util: 0, carbon: 0, energy: 0, grid-carbon-intensity: 471},
149
+
150
+ ]
151
+ ```
152
+
153
+
154
+ #### Resampling rules
155
+
156
+ Now we have synchronized, continuous, high resolution time series data, we can resample. To achieve this, we use `interval`, which sets the global temporal resolution for the final, processed time series. `intervalk` is expressed in units of seconds, which means we can simply batch `observations` together in groups of size `interval`. For each value in each object we either sum, average or copy the values into one single summary object representing each time bucket of size `interval` depending on their `aggregation-method` defined in `units.yaml`. The returned array is the final, synchronized time series at the desired temporal resolution.
157
+
158
+ ### Assumptions and limitations
159
+
160
+ To do time synchronization, we assume:
161
+
162
+ - There is no environmental impact for an application when there is no data available.
163
+ - Evenly distributing the total for a `duration` across higher resolution `observations` is appropriate, as opposed to having some non-uniform distribution.
164
+
165
+
166
+ ## Typescript implementation
167
+
168
+ To run the model, you must first create an instance of `SciModel` and call
169
+ its `configure()` method. Then, you can call `execute()` to return `sci`.
170
+
171
+ ```typescript
172
+ const timeSyncModel = new TimeSyncModel();
173
+ timeSyncModel.configure('name', {
174
+ 'start-time': '2023-12-12T00:00:00.000Z',
175
+ 'end-time': '2023-12-12T00:00:30.000Z',
176
+ interval: 10
177
+ })
178
+ const results = timeSyncModel.execute([
179
+ {
180
+ timestamp: 2023-12-12T00:00:00.000Z
181
+ duration: 10
182
+ cpu-util: 10
183
+ carbon: 100
184
+ energy: 100
185
+ requests: 300
186
+ },
187
+ {
188
+ timestamp: 2023-12-12T00:00:10.000Z
189
+ duration: 10
190
+ cpu-util: 20
191
+ carbon: 100,
192
+ energy: 100,
193
+ requests: 380
194
+ }
195
+ ])
196
+ ```
197
+
198
+ ## Example impl
199
+
200
+ IEF users will typically call the model as part of a pipeline defined in an `impl`
201
+ file. In this case, instantiating and configuring the model is handled by
202
+ `impact-engine` and does not have to be done explicitly by the user.
203
+ The following is an example `impl` that calls `time-sync`:
204
+
205
+ ```yaml
206
+ name: time-sync-demo
207
+ description: impl with 2 levels of nesting with non-uniform timing of observations
208
+ tags:
209
+ initialize:
210
+ models:
211
+ - name: teads-curve
212
+ model: TeadsCurveModel
213
+ path: "@grnsft/if-unofficial-models"
214
+ - name: sci-e
215
+ model: SciEModel
216
+ path: "@grnsft/if-models"
217
+ - name: sci-m
218
+ path: "@grnsft/if-models"
219
+ model: SciMModel
220
+ - name: sci-o
221
+ model: SciOModel
222
+ path: "@grnsft/if-models"
223
+ - name: time-synchronization
224
+ model: TimeSyncModel
225
+ path: builtin
226
+ config:
227
+ start-time: 2023-12-12T00:00:00.000Z # ISO timestamp
228
+ end-time: 2023-12-12T00:01:00.000Z # ISO timestamp
229
+ interval: 5 # seconds
230
+ graph:
231
+ children:
232
+ child: # an advanced grouping node
233
+ pipeline:
234
+ - teads-curve
235
+ - sci-e
236
+ - sci-m
237
+ - sci-o
238
+ - time-synchronization
239
+ config:
240
+ teads-curve:
241
+ thermal-design-power: 65
242
+ sci-m:
243
+ total-embodied-emissions: 251000 # gCO2eq
244
+ time-reserved: 3600 # 1 hour in s
245
+ expected-lifespan: 126144000 # 4 years in seconds
246
+ resources-reserved: 1
247
+ total-resources: 1
248
+ sci-o:
249
+ grid-carbon-intensity: 457 # gCO2/kwh
250
+ children:
251
+ child-1:
252
+ inputs:
253
+ - timestamp: 2023-12-12T00:00:00.000Z
254
+ duration: 10
255
+ cpu-util: 10
256
+ carbon: 100
257
+ energy: 100
258
+ requests: 300
259
+ - timestamp: 2023-12-12T00:00:10.000Z
260
+ duration: 10
261
+ cpu-util: 20
262
+ carbon: 200
263
+ energy: 200
264
+ requests: 380
265
+
266
+ ```